Files
NianAIGC/findings.md

81 KiB
Raw Blame History

Findings & Decisions

Requirements

  • User asked: "了解整个项目" — inspect and understand the whole project, then explain it clearly.
  • Expected output: concise but useful project map in Chinese, including purpose, stack, structure, runtime flow, commands, and notable risks.

Research Findings

  • Top-level project is not a Git repository; git status --short returned "fatal: not a git repository".
  • Root contains orchestration/docs files plus a removed extracted runtime application directory.
  • removed extracted runtime/node_modules is present, so dependencies appear already installed for the embedded runtime app.
  • The initial full file scan showed many bundled media assets under removed extracted runtime/public, especially starter/planning/Seedance examples.
  • README.md states this was extracted from the 智念助手 desktop app into an independent 智念创作助手 project.
  • The project is based on an existing extracted runtime Next.js standalone runtime; original source was deleted, so this is not a full source restoration.
  • Root package.json only orchestrates scripts: start/dev call removed runtime start script, health calls scripts/health-check.mjs, and info calls removed runtime info script.
  • Runtime package uses Next ^15.1.4, React ^19.0.0, Supabase client, Ali OSS, lucide-react, TypeScript, Vitest, and ESLint, but it is treated as generated runtime.
  • Runtime state should be written to root .runtime/, not under removed extracted runtime.
  • removed runtime start script loads .env and .env.local, optionally bundled .env.runtime only when ZHINIAN_LOAD_BUNDLED_ENV=1.
  • Startup creates .runtime/data, .runtime/uploads, and .runtime/generated-results, then launches removed extracted runtime/server.js with NODE_ENV=production.
  • Health check targets /api/desktop/health and expects JSON with appId: "removed-runtime" and ok: true.
  • .env.example shows the real generation path depends on Seedance / Volcengine Ark plus Aliyun OSS configuration.
  • Extraction notes confirm copied assets include Next standalone server runtime, .next output, runtime node_modules, public/reference media, content manifests, and planning cases; secrets, user uploads, generated results, and Electron host/process manager code were excluded.
  • npm run info succeeded and reports runtime app id removed-runtime, bundle timestamp 2026-05-14T04:01:58.653Z, entry server.js, and size 949,760,759 bytes.
  • App routes include: /, /studio, /studio/[mode], /planning, /projects, /projects/[id], and /billing.
  • API routes include: /api/assets, /api/assets/upload, /api/billing, /api/desktop/health, /api/generations, /api/generations/[id], /api/generations/[id]/retry, /api/projects, /api/projects/[id], /api/prompt/assemble, and /api/reference-templates.
  • File-serving routes expose runtime uploads and generated results via /uploads/[...path] and /generated-results/[...path].
  • Creation modes currently report one mode: video_studio / 宣传片创作台, editor type storyboard_cards.
  • Starter catalog has 14 cases across storefront_avatar_storyboard, music_sync_ad, and creative_remix; planning cases include five visible categories such as short-video promo and premium-brand.
  • Compiled API code reveals a local JSON store at app-state.json with users, assets, projects, generation_jobs, and credit_transactions.
  • The default local user is demo-merchant / demo@localmerchant.ai with 9999 demo credits.
  • /api/projects returns projects with their jobs; project creation is coupled to generation creation and deducts credits based on duration/resolution/ratio.
  • /api/assets/upload accepts multipart file, role, and optional promptLabel; it stores to Ali OSS when OSS env is complete, otherwise writes to local uploads and records an asset.
  • /api/prompt/assemble builds a Chinese Seedance-style video prompt from shop/project details, selected template, storyboard, and optional avatar/outfit selection.
  • Seedance client defaults: base URL https://ark.cn-beijing.volces.com/api/v3, model doubao-seedance-2-0-260128, ratio 9:16, duration 15, resolution 720p.
  • Real generation creation posts to /contents/generations/tasks; query uses /contents/generations/tasks/{id}.
  • Missing SEEDANCE_API_KEY causes a user-facing error and the project/job path refunds credits after marking the job failed.
  • Health response includes services.seedanceConfigured and services.objectStorageConfigured.
  • GET /api/generations/:id reads a local job, polls Seedance when provider_job_id exists, and backs up successful result videos to OSS or local generated-results.
  • POST /api/generations/:id/retry loads the original project settings and re-runs the generation creation flow.
  • GET /api/projects/:id returns one project with jobs; DELETE /api/projects/:id removes the project, jobs, and related credit transactions.
  • GET /api/assets returns local assets for the demo owner; GET /api/billing returns demo user and credit transactions.
  • GET /api/reference-templates?mode=... returns starter templates filtered by mode.
  • Content definition has one canonical creation mode: video_studio, product name 宣传片创作台, editor type storyboard_cards, reference-first workflow, and six asset slot labels.
  • Starter catalog contains 14 selectable templates and 85 local asset records from the Seedance guide plus local promo examples.
  • Template categories represented in content are storefront_avatar_storyboard, music_sync_ad, and creative_remix; docs say legacy mode arguments are accepted for compatibility.
  • Planning page content has five planning cases: 短视频宣传类, 剧情宣传类, 热门玩梗类, 卡通 IP 类, and 品质高级类.
  • Avatar presets currently include one default digital-human model and one default outfit pairing.
  • Runtime verification passed: npm run info succeeded, npm start launched Next on http://127.0.0.1:3000, and npm run health returned ok: true.
  • Health verification reports seedanceConfigured: false and objectStorageConfigured: false, matching the empty local env configuration.
  • Direct curl verification with --noproxy '*' returned 200 OK for /studio, {"projects":[]} for /api/projects, one template for /api/reference-templates?mode=video_studio, and the demo billing user.
  • Starting the runtime created .runtime/data/app-state.json with the demo user and initial credit transaction.

Technical Decisions

Decision Rationale

2026-08-11 - Task Module Consolidation Findings

  • The visible result directory is the top navigation item 结果, backed by /assets and components/asset-manager.tsx; it currently exposes both an asset gallery and a task-history view.
  • The create page already has an independent right-side 任务模块 in components/create-studio.tsx. Each task card currently shows a thumbnail, prompt-derived name, status, elapsed time, billing label, and a 查看详情 link that navigates to /assets?view=tasks&taskId=....
  • The task module already loads /api/assets, /api/generations/image, and /api/generations/video, so it has the local asset records needed for previews and downloads.
  • GenerationJob persists prompt, inputAssetIds, inputUrls, requestPayload, provider, reqKey, status/timing/error, output asset IDs, and billing metadata. Image jobs store the original input under requestPayload.input; video jobs additionally store assembled materials and normalized requestPayload.settings.
  • Existing first-party asset download route is /api/assets/:id/download; generated result storage is served through /generated-results/[...path] and local files live under .runtime/generated-results.
  • Current physical .runtime/generated-results contains local verification data and must not be deleted merely to remove the visible result page; deleting that directory would remove task preview/download backing files.

Issues Encountered

Issue Resolution
git status cannot run because the project root has no .git metadata Treat this as a plain project folder and avoid Git-based assumptions
zsh treats [id] in file paths as a glob pattern Quote bracketed Next.js dynamic route paths when reading them
Initial plain curl calls hit a local proxy and returned 502/empty output Use curl --noproxy '*' for localhost verification

Resources

  • Project root: /Users/inmanx/Documents/zhinian-creation-assistant
  • Runtime app: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime
  • Root README: /Users/inmanx/Documents/zhinian-creation-assistant/README.md
  • Runtime README: /Users/inmanx/Documents/zhinian-creation-assistant/runtime/README.md
  • Startup script: /Users/inmanx/Documents/zhinian-creation-assistant/removed runtime start script
  • Health script: /Users/inmanx/Documents/zhinian-creation-assistant/scripts/health-check.mjs
  • Runtime info script: /Users/inmanx/Documents/zhinian-creation-assistant/removed runtime info script
  • Extraction notes: /Users/inmanx/Documents/zhinian-creation-assistant/docs/EXTRACTION_NOTES.md
  • App paths manifest: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app-paths-manifest.json
  • Compiled projects API: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/projects/route.js
  • Compiled upload API: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/assets/upload/route.js
  • Compiled prompt assembly API: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/prompt/assemble/route.js
  • Compiled generation polling API: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/generations/[id]/route.js
  • Compiled generation retry API: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/generations/[id]/retry/route.js
  • Creation modes JSON: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/content/seedance-starter/creation-modes.json
  • Starter catalog JSON: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/content/seedance-starter/catalog.json
  • Planning cases JSON: /Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/content/removed planning case manifest.json
  • Runtime local state file: /Users/inmanx/Documents/zhinian-creation-assistant/.runtime/data/app-state.json

Visual/Browser Findings

  • 2026-05-29 UI polish verification:
    • /create, /create?mode=video, /assets, and /settings were checked during the product polish work.
    • 375, 768, 1024, and 1440 width checks showed no horizontal overflow after the compact header and mobile control changes.
    • The video duration dropdown on /create?mode=video exposes only 4 秒 through 15 秒.
    • The header logo loads from public/logo/zhinian-logo.png; after the final branding pass it has no border, background, or box shadow.

2026-05-29 UI/UX and Branding Findings

  • The current source app is now a Web app in the repository root, not the old removed extracted runtime standalone-only flow described in the earliest findings.
  • GSAP is used through lib/ui/motion.ts rather than directly sprinkled across components.
  • The UI direction is a professional creation workspace, not a marketing landing page.
  • Visible English eyebrows/descriptions were removed from module headers per user preference.
  • Topbar should remain compact and avoid horizontal scrolling below it.
  • Product name is now 智念AIGC平台.
  • Logo source folder: /Users/inmanx/Documents/icon/logo.
  • Current logo asset: public/logo/zhinian-logo.png.
  • Current logo was generated from /Users/inmanx/Documents/icon/logo/2d5b992caa14db16f594c4933e92e37e.png by removing the white background and cropping whitespace.
  • Avoid using the white transparent logo on the light topbar unless the topbar itself becomes dark; wrapping the logo in a dark frame changes the brand feel.

2026-05-29 Seedance Findings

  • Official Volcengine Ark "创建视频生成任务 API" docs say Seedance 2.0 duration supports integer seconds in [4, 15], or -1 for model-chosen duration.
  • Seedance 2.0 and Seedance 1.5 Pro support adaptive ratio behavior.
  • Supported ratios include 16:9, 4:3, 1:1, 3:4, 9:16, 21:9, and adaptive.
  • Supported resolutions include 480p, 720p, and 1080p, but Seedance 2.0 fast does not support 1080p.
  • frames is not supported for Seedance 2.0 / Seedance 1.5 Pro, so the current app should continue using integer duration.
  • generate_audio defaults true in the API and remains enabled in the app payload.

2026-05-29 Operational Findings

  • For localhost verification, continue using curl --noproxy '*' because local proxy settings can interfere with direct checks.
  • Before running npm run build, stop the dev server (screen -S zhinian-dev-ui -X quit and pkill -f 'next dev --hostname 127.0.0.1 --port 3000') to avoid stale Next dev chunk issues.
  • After build verification, restart the dev server in screen session zhinian-dev-ui on 127.0.0.1:3000.

2026-05-29 Deployment Findings

  • Server one-command deployment is now bash scripts/deploy.sh.
  • Docker Compose service name is zhinian-aigc.
  • Docker Compose defaults to exposing host port 3000; set APP_PORT in .env.local or shell to change it.
  • NEXT_PUBLIC_APP_URL should be set to the public domain or server URL in production so generated local file URLs are correct.
  • Persistent runtime data is bind-mounted through ./.runtime:/app/.runtime; this folder should be backed up on real servers.
  • .env.local is intentionally used as the compose env_file and remains ignored by Git.
  • Current local machine does not have the Docker CLI available, so Docker build was not run here; script syntax, app tests, production build, and local health were verified instead.

2026-05-29 Public API and Task Management Findings

  • User confirmed multi-task support should be task management logic, not an external message queue.
  • Public API v1 now uses ZHINIAN_API_KEYS, supporting Authorization: Bearer <key> and X-Zhinian-Api-Key.
  • Task creation and provider execution are now split: submit routes enqueue GenerationJob records; Worker ticks claim and process jobs.
  • generation_jobs now carries external client, idempotency, priority, attempts, lock, schedule, timing, and webhook fields.
  • Supabase/Postgres production mode expects the claim_generation_jobs function from supabase/schema.sql for atomic task claiming.
  • Local JSON mode serializes task claiming through the existing local write queue and is intended for single-instance development.
  • Worker execution can run as npm run worker, npm run worker:once, or the zhinian-worker Docker Compose service.
  • Internal Worker processing goes through /api/internal/worker/tick protected by ZHINIAN_INTERNAL_WORKER_TOKEN in production.
  • API v1 routes are /api/v1/capabilities, /api/v1/assets, /api/v1/jobs, /api/v1/jobs/:id, /api/v1/jobs/:id/cancel, and /api/v1/openapi.json.
  • Local verification created a public API job and processed it to succeeded through npm run worker:once in mock mode.
  • Public API asset access now includes /api/v1/assets/:id and /api/v1/assets/:id/download.
  • Uploaded and generated assets created through public API flows are tagged as api-client:<clientId> so integrations can query and download their own results later.
  • OpenAPI is generated dynamically from the current deployment origin at /api/v1/openapi.json.
  • Operations handoff docs live in docs/DEPLOYMENT.md; partner API docs live in docs/API.md.

2026-05-29 Image Tuning Findings

  • Jimeng image generation supports the current scale parameter, so UI presets should submit numeric text-influence values for that engine.
  • EvoLink image generation does not use Jimeng scale; the per-request engine-aware control should submit EvoLink quality instead.
  • Current local /api/health reports image.generate using EvoLink, so /create should show 生成质量 options rather than 文本影响.

2026-05-29 Account Login / SSO Findings

  • User requested account login before release so the project is safe to use.
  • Provided SSO guide recommends OAuth2 Authorization Code for Web SSO: redirect to ${AUTH_BASE}/oauth2/authorize, receive code and state, then exchange code server-side at ${AUTH_BASE}/oauth2/token.
  • OAuth client defaults in the guide use client_id=customPC and scope=server; client_secret must stay on the server.
  • Access tokens are JWTs; resource services should verify locally with JWKS from ${AUTH_BASE}/oauth2/jwks rather than calling auth on every request.
  • Minimum JWT checks from the guide: RS256 signature, exp, nbf/iat, issuer https://pig4cloud.com, OAuth client id, scope/authority requirements.
  • Logout endpoint is DELETE ${AUTH_BASE}/token/logout, but local session deletion remains required because existing JWTs may stay valid until exp.
  • Current app has no login middleware or session helper; pages are client components under a global shell.
  • Current local data store defaults all assets/jobs to DEFAULT_OWNER_ID = "demo-merchant", so account login must also address per-user owner IDs for first-party UI APIs.
  • Public API v1 already has separate API key auth through ZHINIAN_API_KEYS; SSO should preserve that server-to-server surface.
  • First-party UI APIs that currently need session ownership include /api/assets, /api/assets/upload, /api/assets/:id/*, /api/generations/image*, /api/generations/video*, and /api/settings.
  • Generation services already accept optional ownerId, so route handlers can pass the authenticated owner without rewriting provider dispatch or worker logic.
  • Retry helpers currently preserve the original request payload; they need to override ownerId on retry so a user cannot retry another user's job if they know the id.
  • /uploads/* and /generated-results/* serve local runtime files directly; middleware should protect these paths for cookie-authenticated browser sessions.
  • /api/v1/* and /api/internal/worker/tick must remain outside browser SSO middleware because they use API keys and worker tokens.
  • Implemented browser SSO with signed HttpOnly zhinian_session cookies; middleware validates the signed session instead of exposing JWTs to client JavaScript.
  • JWT access tokens are verified in the callback using RS256 and configured JWKS, with issuer, client id, expiry, not-before, issued-at, and scope checks.
  • Local file routes now resolve storagePath back to an asset record and require the current owner to match before serving bytes.
  • Public API v1 remains API-key based and can still read/download its assets through public API routes even when browser SSO protects the Web UI.

2026-05-29 Password Captcha Login Findings

  • User provided live auth configuration and a password grant sample; real secret values remain only in the ignored local environment file.
  • ${AUTH_BASE}/code/image?randomStr=... returns a PNG captcha image.
  • The password grant sample successfully returns a JWT access token, refresh token, expected client/user claims, tenant id, and server scope.
  • The local /api/auth/password endpoint verified the returned JWT with JWKS and created a signed browser session for the authenticated user.
  • Browser form login with the displayed math captcha succeeded and redirected to /create; the topbar showed the authenticated username and logout button.
  • Logout cleared the session and returned to /auth/login?loggedOut=1.

2026-05-29 Standalone Login Page Findings

  • Login routes under /auth/* should not render the shared app topbar; the login page is a standalone entry surface.
  • The login page now intentionally presents only the NIANXX logo, 智念AIGC平台, and the account/password/captcha form.
  • The visible 统一认证中心 OAuth entry was removed from the login page after user feedback.
  • The standalone login page uses the existing GSAP motion helper layer (runScopedMotion, revealChildren, pulseFeedback) for consistent app motion.
  • Browser viewport checks passed at 1280x800 and 390x844: no topbar, no SSO link text, logo and platform name present, login panel present, and no horizontal overflow.

2026-06-09 Repository and Runtime Status Findings

  • The current repository root is a Git checkout on main tracking origin/main.
  • The remote is https://git.nianxx.cn/wangxuming/NianAIGC.git.
  • The workspace was overwritten from remote and is currently clean at d98e58a docs: update public api docs.
  • The project uses npm (package-lock.json) and Next.js dev startup through npm run dev.
  • Local Node version during verification was v22.22.1, satisfying the package.json >=20 engine.
  • During startup verification, ports 3000, 3001, and 3002 were already occupied by local Node/Next processes or listeners; use a confirmed-free alternate port rather than assuming 3000.
  • npm run dev -- --hostname 127.0.0.1 --port 3003 successfully started the current project and reached Next Ready in 2.9 seconds.
  • A smoke request to http://127.0.0.1:3003/ returned 307 Temporary Redirect to /auth/login?next=%2F, confirming the login-protected app flow.
  • Current status check on 2026-06-09 found no next dev / next-server process and no listener on 3003; the project is verified startable but not currently running.

2026-07-01 Account ID Partitioning Findings

  • First-party browser APIs already pass the authenticated session user id into asset and generation flows.
  • Public API v1 currently authenticates by clientId:key, but jobs and assets are still created with ownerId = DEFAULT_OWNER_ID.
  • Public API listing/detail access filters with externalClientId and api-client:<clientId> tags, which works as an access check but keeps all API account data in one default owner partition.
  • The data store and Supabase schema already support owner_id indexes and idempotency uniqueness by (owner_id, external_client_id, idempotency_key), so the change can be made by deriving a stable account owner id from the API client id.
  • Job detail and cancel routes currently check only externalClientId; they should also require the account owner id so a client id collision cannot cross account partitions.
  • Implemented owner derivation as publicApiOwnerId(client), yielding api:<sanitizedAccountId>.
  • Public API job creation, idempotency lookup, job listing/detail/cancel, asset upload/register/list/detail/download now use the derived API account owner.
  • Documentation now states that ZHINIAN_API_KEYS uses 账号ID:key and that records are partitioned under api:<账号ID>.

2026-07-01 Password Login Captcha Finding

  • origin/main is up to date at d98e58a; relevant history includes ce358df 修改认证中心对接方式 and 288e31d 移除验证码输入框.
  • Current components/auth-login-panel.tsx does not render captcha controls and submits only username, password, and next.
  • Current app/api/auth/password/route.ts no longer requires captcha; it only forwards code and randomStr if they are present in the request body.
  • Current local .env.local points password login at https://onefeel.brother7.cn/ingress/auth with client id customPC.
  • A local login probe with fake credentials returned 验证码不能为空; a direct token request to the configured auth center with the same non-secret dummy credentials also returned 验证码不能为空.
  • Therefore the observed captcha error is coming from the configured auth center instance, not from the current AIGC frontend or password route validation.
  • The operations SSO guide defaults external projects to OAuth client app/app, recommends adding token-flow clients to security.ignore-clients, and notes customPC is a special platform-user compatibility client that skips AES password decrypt.
  • Dummy token probes showed app/app reaches normal credential validation (用户名或密码错误) while customPC is intercepted by captcha validation on the current auth center instance.
  • Updated the project defaults, docs, .env.example, and local .env.local to use app/app plus ZHINIAN_AUTH_PASSWORD_ENC_KEY=thanks,pig4cloud.
  • After restarting the dev server, local /api/auth/password with dummy credentials returns 用户名或密码错误, confirming the captcha interception is bypassed for the configured client.

2026-07-01 Internal RBAC / Account Management Findings

  • Initial requested guide /Users/inmanx/Desktop/organization-external-api.md was missing; updated guide /Users/inmanx/Desktop/organization-external-api(1).md is available and has 1310 lines.
  • Existing JWT session already stores authorities and scope on AuthUser, making it suitable for internal RBAC.
  • Before the RBAC change, navigation exposed 创作, 结果, 日志, and 设置 to every logged-in user.
  • Before the RBAC change, middleware only checked whether a user was authenticated and did not distinguish ordinary users from administrators.
  • Before the RBAC change, /api/settings checked requireAppUser() but not admin permission.
  • Before the RBAC change, /api/logs relied on middleware authentication only and did not call requireAppUser() or admin checks inside the route.
  • RBAC now hides admin navigation for ordinary users and protects /logs, /settings, /accounts, /api/logs, /api/settings, and /api/admin/*.
  • Updated guide covers organization, department, role, member, and enterprise user operation APIs.
  • Organization/member APIs are served by basic-capability-services-biz; enterprise user management APIs are exposed by hotel-staff-server-biz.
  • Member list APIs remain @Inner and require header from: Y.
  • Enterprise user APIs require Authorization and a token with administrator role 1; otherwise they return 仅管理员角色允许调用.
  • Account creation should use /adminOrganization/organizationMember/addOrganizationMemberAndCreatePlatformUser to create the enterprise user and bind the organization member in one call.
  • Password maintenance is available through /adminPcUser/resetPlatformUserPassword, with tenantId, numeric userId, newPassword, and optional mustChangePassword.
  • Updated /Users/inmanx/Desktop/organization-external-api(2).md confirms the external member list path is /adminOrganization/organizationMember/organizationMemberList through hotelStaff; only the basic service internal /organizationMember/organizationMemberList path needs from: Y.
  • The updated hotelStaff member-list proxy is reachable from the app, but the current login token can still be rejected by the upstream service with 仅管理员角色允许调用.
  • Local RBAC allow-listing ceshiop as a platform admin only controls this app's pages and APIs; it does not grant upstream hotelStaff administrator role 1 inside the authentication center token.
  • Account management now treats upstream member-list administrator denial like a recoverable member-list-only limitation: the page can still load configured organization data and keep account creation/password maintenance available when those upstream endpoints allow the token.
  • The updated organization guide exposes department creation at POST /organizationGroup/createOrganizationGroup with organizationId, groupName, optional groupDesc, and optional parentId.
  • The account page can create a department from the member form and then refresh/select the created department, so admins do not need to leave account management before creating members.

2026-07-02 Image Template Findings

  • User requested image-generation templates with selection-time effect preview and preset prompt support.
  • This should be account-managed data, not global environment configuration.
  • Existing browser image generation API already uses requireAppUser() and submits ownerId: user.id, so template APIs should use the same owner boundary.
  • Template configuration should remain inside the image generation module rather than the global settings page.
  • The create page already keeps image/video prompt state client-side, detects the active image engine from /api/health, and submits prompt, materials, size, force-single, and engine-specific tuning to /api/generations/image.
  • Implemented image templates with fields for name, category, description, preset prompt, preview image URL, width/height, force-single, and sort order.
  • New first-party routes are /api/image-templates and /api/image-templates/:id; both require the app user and use the authenticated user id as owner id.
  • Local JSON state now normalizes imageTemplates; Supabase deployments need the new image_templates table from supabase/schema.sql.
  • Browser verification used a temporary auth-disabled dev server to create one demo template, confirm the /create left template rail and template application behavior, then deleted the demo template.
  • Final dev server was restarted with the normal .env.local auth-enabled environment on 127.0.0.1:3001.

2026-07-02 Large Auth Session Finding

  • A new real account could receive a successful /api/auth/password response and still be redirected back to /auth/login?next=/create, which indicates the browser did not persist a valid session rather than an upstream password failure.
  • The session cookie stores the signed session plus access token so the app can forward the current login token to organization/staff APIs.
  • Accounts with larger JWT or authority payloads can exceed a single browser cookie's practical size limit; splitting zhinian_session into chunked cookies keeps the same signed payload while allowing middleware and server helpers to reassemble it.
  • Settings no longer exposes a template tab.
  • In image mode, /create now renders a left vertical 模板选择 rail with an icon 添加模板 button and thumbnail/name-only template cards.
  • The add-template entry opens an in-module template form modal; it is not exposed from settings.
  • Clicking a template selects it and brings its preset prompt, image size, and force-single value into the generation console on the right.
  • Desktop and narrow browser verification confirmed the template picker is an independent left-side panel beside the generation panel, not a child of the generation panel, with no horizontal overflow.
  • The image/video/edit mode switch is now contained by the generation panel, not placed above or across the far-left template module.
  • Template preview image upload now uses the same /api/assets/upload route as workbench materials, so configured OSS storage is reused instead of accepting a manually typed preview URL.
  • New template configuration no longer exposes category or remark fields; the user-facing free text field is 简介, backed by the existing template description data.
  • Template preset prompts can contain @图片1 style placeholders. The generation console parses those tokens and shows missing upload slots below the prompt.
  • Placeholder upload slots bind the uploaded asset to the exact requested token, so clicking the @图片2 slot creates/updates the @图片2 material instead of relying on upload order.
  • Material labels are preserved after removal to avoid silently breaking prompt references created by templates.
  • UI/UX skill guidance for the add-template modal was applied as a focused SaaS workspace form: strong preview region, fewer visible decisions at once, clear bottom actions, and responsive stacking on narrow screens.
  • While editing a template prompt, parsed placeholder chips are shown below the textarea so users can confirm which upload slots the template will request after selection.
  • Image templates now store the target image generation engine in settings.engine, with Jimeng using settings.scale and Image2/EvoLink using settings.quality.
  • First-party image generation now accepts a per-request engine override and passes it into submitImageJob(), so template engine choices are honored by the provider payload.
  • The generation console exposes the current image engine selector; applying a template updates this selector plus the matching parameter control.
  • Material placeholders now require an explicit number, so @图片1 and @图2 create upload slots while bare @图片 or @图片这种普通文字 stay plain prompt text.
  • Template prompt editing includes explicit placeholder insertion controls for image/video/audio slots, making the placeholder confirmation action deliberate instead of relying on unfinished @ typing.
  • Existing image templates can be reopened from the top-right edit action on each template card and saved through the account-scoped PATCH route.
  • The template rail is intentionally wider on desktop and each card separates image preview, template selection, and template editing into distinct controls.
  • Clicking a template thumbnail opens a larger preview dialog; choosing from that dialog applies the template and closes the preview.
  • Typing @ in prompt editors now opens a temporary material draft slot instead of immediately writing a partial token into the prompt body.
  • A material draft is confirmed into the prompt only by Enter or explicit option selection; confirmed values are inserted with text boundaries so subsequent Chinese text is not absorbed into the token.
  • The temporary draft slot is shared by the generation prompt and template preset prompt; invalid text such as 图片这种普通文字 stays in the slot and does not create upload requirements.
  • Confirmed numbered tokens keep using the existing visual token overlay, placeholder chips, and missing upload-slot UI.
  • The temporary material draft slot should remain visually inside the prompt editor surface, not below the input, so @ entry feels like an inline editing affordance.
  • Template preset prompts use the same token overlay as the generation prompt; confirmed placeholders such as @视频1 are visibly colored inside the input area before saving the template.
  • The image template rail now uses a wider desktop/tablet track (390-480px desktop, 315-375px tablet) and stacks above the generation panel on mobile so reference previews stay browseable.
  • Template thumbnails use a 4:3 viewport with contained images, allowing both 9:16 and 16:9 reference images to be inspected without cropping.
  • Template card application now requires the explicit 选择模板 button; thumbnail clicks open preview and template name/description text no longer selects the template.
  • The template rail remains visible in image and video generation modes; only the image-editing modes (局部重绘, 智能超清) use the standalone editor layout without the template rail.
  • Template cards are fixed compact items (138px x 184px) inside the rail list, so each template has a consistent smaller footprint.
  • The template rail height is synchronized from the right generation panel through a ResizeObserver; the rail body is split into fixed header/action rows and a scrolling template-list row.
  • The scrollbar belongs only to .image-template-rail-list; 模板选择 and 添加模板 stay outside the scroll container.
  • Template rail cards now use their preview image as a full-bleed card surface with object-fit: cover.
  • Template card title, intro, and select action sit inside a bottom floating frosted-glass overlay; the select button remains the only action that applies the template.
  • The zoom icon was removed from template cards, while the edit icon remains on the top-right corner above the image.
  • Selected template cards use a heavier layered shadow and slight upward transform to create stronger depth.
  • The template intro/description field is no longer part of the create/edit template UI or card display; old stored descriptions may still exist in data but are ignored by this surface.
  • The template edit icon now sits inside the card's frosted-glass overlay instead of on the image corner.
  • Template selection is a toggle: the first click captures the current right-side image generation state and applies the template; clicking the selected template again restores that snapshot and clears the active template state.
  • The compact full-bleed template card target is 132px x 176px; the widened desktop rail should fit three cards per row.
  • The card footer uses a two-row frosted-glass grid: title spans the first row, while the select/cancel button and edit icon share the second row to avoid cramped text or overlap.

2026-07-02 Create Task Module Findings

  • The creation console can reuse existing first-party APIs for a task list: /api/generations/image, /api/generations/video, and /api/assets.
  • GenerationJob already has the needed task fields: prompt/name fallback, status, created/updated timestamps, capability, and output asset ids.
  • Generated task thumbnails can be resolved by mapping GenerationJob.outputAssetIds to assets from /api/assets; queued/running tasks need a placeholder when no output asset exists yet.
  • The existing results page already has a task view and detail panel, so the create-page 查看详情 action deep-links to /assets?view=tasks&taskId=<id> instead of creating a separate task page.
  • Desktop create layout now supports three independent modules: left template rail, center generation console, and right task module. At narrower widths the task module drops below the main modules, and at mobile widths all modules stack without horizontal overflow.
  • The right task module now targets 560-640px on desktop/wide screens so task name, status, elapsed time, and 查看详情 can stay on one horizontal row.
  • Queued/running jobs without output assets should render a clear 生成中 thumbnail placeholder instead of a generic empty asset icon.
  • Completed image-task thumbnails are preview actions inside /create; clicking them opens the same large asset-preview surface while 查看详情 remains the route to the full task page.

2026-08-11 Platform-Owned Account System Findings

Existing authentication boundary

  • Browser authentication currently depends on an external OAuth2/OIDC-style service: authorization-code callback and password-grant login exchange against ZHINIAN_AUTH_BASE_URL.
  • The app signs the resulting AuthSession into the zhinian_session cookie and derives first-party ownership from external claims such as auth:<clientId>:<subject>.
  • lib/types.ts has only a minimal AppState.users demo shape (id, email, displayName); it is not an authentication user store.
  • supabase/schema.sql has no local users, organizations, memberships, password credential, or audit schema.
  • The current /accounts implementation proxies an external organization/member service and must be replaced for local account management.
  • .runtime/data/web-app-state.json contains a demo user, while historical assets/jobs use external-auth owner IDs; a migration must preserve those links via an import/mapping layer.

Confirmed product decisions

  • Production source of truth: platform-owned user data in Supabase/Postgres; local development fallback: .runtime/data JSON.
  • OAuth2 dependency is removed from browser account authentication.
  • Roles: super_admin, organization_admin, user.
  • Super admins manage all organizations, accounts, logs, system settings, and global usage.
  • Organization admins manage ordinary users in their own organization, reset passwords, and view organization aggregate usage; they do not view logs, manage system settings or organization lifecycle, view member assets/tasks, or grant organization-admin roles.
  • Ordinary users access their own creation/assets/tasks and can change their own password.
  • One account is created once and belongs to one organization only; no cross-organization membership or organization switching.
  • Organizations are created, renamed, disabled, or deleted only by super admins.
  • New accounts are administrator-created; public registration is not provided.
  • Login identifier is a unique immutable phone number; email is removed.
  • Users cannot self-edit their phone number.
  • Admins set initial and reset passwords; users may change passwords in settings; first-login password change is not forced.
  • Accounts can be disabled or permanently deleted. On permanent deletion, login identity is removed, usage records are retained for finance reconciliation, and assets/tasks are transferred to an organization archive owner.
  • Legacy accounts and history are migrated by phone/account mapping; external password hashes are not migrated, so administrators set new passwords without forced first-login change.
  • Unified phone/password login is used for all roles; users do not select a role at login.
  • Login security: five failed attempts trigger a 15-minute account lock, with IP-level request limiting.
  • The first super admin is created through a one-time initialization command.

Implementation defaults pending no further product decision

  • Use Node's built-in crypto.scrypt password hashing with per-user salts and constant-time verification; never store plaintext passwords.
  • Keep the existing signed/chunked HttpOnly session-cookie mechanism, but issue sessions from local user records rather than external JWT claims.
  • Treat the migration export/mapping file as an operator-provided input; do not invent legacy account rows that are not present in the repository.

2026-08-11 Implementation result

  • Platform browser authentication is now first-party and uses only the immutable phone identifier plus a local password; the old OAuth routes remain as compatibility redirects/deprecated helpers and are not part of browser authentication.
  • Supabase production tables and local JSON fallback share the same account-store contract, so login, role checks, lockout, organization scoping, and password changes do not depend on the external organization service.
  • Organization status is enforced at login/session refresh. Disabling an organization therefore blocks its organization-admin and ordinary-user sessions while preserving their records for later reactivation or audit.
  • Organization-admin usage responses intentionally omit account-level options/details; only the organization aggregate view is exposed to that role.
  • Account hard deletion removes the login identity, reassigns assets/jobs/projects/templates to the organization archive owner, and leaves usage events untouched for finance reconciliation.
  • Final local verification passed with 22 test files / 80 tests, TypeScript, script syntax checks, production build, health endpoint, login page, and role-boundary HTTP smoke tests.

Session: 2026-08-11 - Enterprise Billing Discovery

Official pricing catalog findings — 2026-08-11

  • Alibaba Model Studio wan2.7-image-pro Beijing price is ¥0.50/image; wan2.7-i2v is ¥0.60/second at 720P and ¥1.00/second at 1080P. The current environment uses the dated wan2.7-i2v-2026-04-25 model key, so the default rule follows that key.
  • Volcengine Ark doubao-seedance-2.0 official pricing is token-based. For the documented no-input-video, 16:9, 5-second example, the output-only totals are ¥2.31 at 480P, ¥4.97 at 720P, and ¥12.39 at 1080P; the catalog stores those as ¥0.46, ¥0.99, and ¥2.48 per output second after fen rounding.
  • EvoLink GPT Image 2 is token-based. The official estimator page gives approximately $0.047 for medium quality, 1K, 1:1, and no reference image; with the confirmed fixed FX rate of 7.20, the catalog stores ¥0.34/image as a rounded baseline rather than claiming a fixed provider bill.
  • Jimeng/Volcengine Visual 4.6's official billing page says the latest price is shown in the console and bills by successful call. The catalog therefore uses the public ¥200/1000-image activity package only as an enabled, editable reference baseline of ¥0.20/image and labels it for administrator review.
  • Default rules are inserted only when the same provider/capability/model/variant is absent. A pre-existing generic rule for a provider/capability suppresses automatic defaults for that scope, preserving administrator fallback behavior.

Confirmed from current code

  • Platform-owned accounts and roles already exist: super_admin, organization_admin, and user.
  • Platform generation jobs carry usageContext with account and organization identity when a first-party session is available.
  • UsageEvent currently records capability/provider/account/organization and a quantity of 1, but has no provider unit cost, markup, charged amount, currency, wallet, recharge, or ledger reference.
  • recordUsageForJob() is idempotent by jobId, excludes mock and public API jobs, and currently only creates an analytics event after the job path is established; it is not a financial debit.
  • Existing usage reports aggregate task counts and are already role-aware through /api/usage and /api/admin/usage; they need to be extended with money and balance views.
  • The data layer supports local JSON fallback and Supabase/Postgres, so billing must preserve both storage contracts and use an atomic/idempotent financial operation in production.
  • Existing workspace has uncommitted account-system changes owned by the user; future billing edits must be additive and avoid resetting unrelated work.
  • The current Node executable is not available as node in the shell PATH during this discovery call; verification should use the workspace dependency/runtime path if needed.

Open decision for user

  • Recommended billing unit: charge each successful provider request using the provider's source-unit price from a super-admin-managed price catalog, then multiply by a configurable organization/platform markup; store the final amount and the exact price/multiplier snapshot on the debit ledger so later price changes cannot rewrite history.

Confirmed decisions and removal scope

  • User confirmed the billing model: provider-native billing quantity × super-admin-managed markup, with the final amount and pricing snapshot retained in the ledger.
  • User requested removal of both image capabilities: image.upscale (高清/智能超清) and image.inpaint (局部重绘).
  • Removal must cover the shared capability types, provider capability builders, generation routes, asset edit routes, create/editor UI, engine/settings status, public API validation/OpenAPI, tests, documentation, and stale environment variables.
  • Remaining first-party generation capabilities are image generation and video generation; mock behavior remains available for local verification.
  • Generation jobs are currently queued before provider execution, so billing integration should reserve funds at submission, release/refund the reservation on provider failure/cancellation/expiry, and finalize the debit on successful completion. This preserves balance safety while making the amount visible on the generation record.

Capability removal implementation map

  • components/create-studio.tsx currently embeds the edit-mode switch and ImageEditor; the remaining studio should use only GenerateMode = image | video.
  • components/image-editor.tsx, app/api/assets/[id]/inpaint/route.ts, and app/api/assets/[id]/upscale/route.ts are capability-specific and can be removed without affecting upload or normal generation.
  • app/image-edit/page.tsx is a legacy compatibility entry; it will redirect to /create without an edit mode so old bookmarks do not expose a removed capability.
  • lib/jimeng/capabilities.ts, lib/evolink/image-client.ts, lib/server/app-settings.ts, public API parsing/OpenAPI, usage labels, and documentation contain stale capability definitions that must be reduced to image/video generation.
  • Historical asset source values edited and upscaled remain readable for backward compatibility, but no new job/API/UI path will create or advertise them.

Billing implementation map

  • Billing amounts use integer fen (CNY) to avoid floating-point ledger drift; price rules hold provider, capability, optional exact reqKey, native unit, standard unit price, and multiplier.
  • A generation job stores a billing quote snapshot in generation_jobs.billing; charge/refund ledger entries are keyed by job-charge:<jobId> and job-refund:<jobId> for idempotency.
  • Real provider jobs require an organization and a matching enabled price rule; mock jobs remain uncharged so local mock mode can still be used before real provider prices are configured.
  • Public API jobs without an organization context remain uncharged for backward compatibility; first-party platform jobs use the authenticated organization context.
  • User billing routes expose organization balance, organization ledger, personal ledger summary, and offline recharge requests; super-admin routes expose price rules, organization wallets, and recharge review operations.

Billing implementation result

  • Added local billing-state.json fallback and Supabase billing_price_rules, billing_wallets, billing_ledger, and billing_recharge_requests tables with the billing_post_wallet_entry atomic RPC.
  • Added corporate transfer account configuration through the super-admin service settings and exposed the account details on the member billing page before recharge submission.
  • Added billing labels to create-task and asset-task views so each generation record exposes the quoted/charged/refunded amount.
  • Final verification uses the project Vitest runner rather than Bun's built-in runner; all 23 test files and 81 tests pass.

Session: 2026-08-11 - Billing Center UI Redesign

Visual audit

  • The previous super-admin page rendered balance cards, account settings, ledgers, price rules, organization wallets, and recharge review as one continuous stack. The long default-rule notes made the pricing area disproportionately tall and weakened the primary balance/action hierarchy.
  • The redesign keeps the page light and editorial-minimal: organization balance is the first visual anchor, personal and organization consumption are secondary, and operational controls are grouped by role and task.
  • Price-source links and explanatory notes remain available but are truncated to compact metadata lines. Custom rule creation is hidden behind an explicit disclosure so the default catalog stays readable.
  • The page uses existing Lucide icons and motion helpers; no new dependency or image asset was needed for this data-heavy internal finance surface.

Tabbed operations and balance semantics

  • The existing billing domain already models one organization wallet plus account-attributed immutable ledger entries. It does not model a personal wallet, so manual member selection must remain attribution-only to avoid contradicting the shared organization balance requirement.
  • Existing super-admin settings persistence already supports the four corporate-account fields through saveApiSettings; the billing center now exposes the same path through a focused billing endpoint instead of duplicating configuration storage.
  • The admin billing payload now includes organizations, members, all recent ledger entries, price rules, and recharge requests so the balance tab can show both organization wallets and per-member net consumption without additional per-row requests.

Billing error recovery — 2026-08-11

  • Local fallback storage reproduces the new billing page and all initial billing endpoints successfully; the reported generic server error is therefore not reproducible without the user's deployed request/response or server log.
  • The most likely external-state failure is an unapplied or partially applied Supabase upgrade: billing tables/RPCs or Phase 53 variant_key/source columns are missing even though the application code expects them.
  • Billing storage now translates matching Supabase schema-cache/relation/column/function errors into an explicit instruction to execute supabase/schema.sql; the client also handles HTML/plain-text 500 responses without throwing a JSON parse error.

Super-admin blank state — 2026-08-11

  • A super-admin is intentionally not required to belong to an organization. The member billing endpoint therefore returns 422 for that identity, which must not prevent the admin billing center from rendering.
  • The super-admin view now treats /api/admin/billing as its source of truth and aggregates organization wallets and ledger entries into a platform overview payload for the balance hero and recent ledger.

Parameterized billing rules — 2026-08-11

  • The billing domain currently stores only variantKey, unit, base unit price, and markup. Matching only reads resolution; it cannot distinguish user-selected quality, size, aspect ratio, or reference-image count.
  • The generation request payload already carries nested settings, providerPayload, and input records. Video requests expose duration/resolution/ratio; image requests expose provider-specific size/quality and image count in their provider payloads/settings.
  • The safe extension is structured JSON conditions plus an explicit quantity source. Legacy variantKey values remain readable and are translated into a resolution condition during matching.
  • Quote selection should prefer exact reqKey and the highest number of matching conditions; equal-specificity matches must be rejected as ambiguous in configuration rather than silently choosing by insertion order.
  • The client may request a quote for preview, but task submission must call the same server matcher again and persist the normalized parameters, matched rule, and final amount snapshot.

Parameterized billing implementation — 2026-08-11

  • BillingPriceRule now supports conditions, quantitySource, and priority; the Supabase schema persists them and the local JSON store canonicalizes condition keys/values for duplicate detection.
  • Normalized billing parameters are derived from the actual provider payload rather than from a client-supplied price. Current UI choices map as follows:画幅→size/aspectRatio, EvoLink quality→quality, Jimeng text influence→scale, video ratio→aspectRatio, video duration→duration, video resolution→resolution, and image/video references→referenceImageCount.
  • A generic rule remains a deliberate fallback. To require every combination to be explicitly configured, the super-admin can disable/remove the generic fallback and keep only conditional rules; an unmatched enabled scope then returns a billing configuration error before a real job is created.
  • The quote snapshot stores the effective legacy/structured conditions, normalized parameter map, quantity source, quantity, markup, and final fen amount. Later price edits therefore cannot rewrite historical ledger entries.

Task detail consolidation — 2026-08-11

  • The user confirmed that “删除结果目录” means removing the visible result-directory experience from the front end, not deleting generated-result files or storage records.
  • The user confirmed that task details, prompt/material inspection, result preview, and download should all be handled inside the /create task module.
  • The user confirmed that the platform is desktop-only; no additional mobile breakpoint work is part of this change.
  • The implementation keeps /api/assets, /api/assets/[id]/download, /api/v1/assets, and local/OSS result storage intact. /assets remains only as a compatibility redirect to /create.

Direct billing top-ups — 2026-08-11

  • The current recharge flow is request/review based: members POST /api/billing with payer and transfer details, the request is stored as pending, and super admins PATCH /api/admin/billing/recharges/:id to approve or reject it.
  • The billing center exposes this workflow through a member 线下充值 tab, a super-admin 充值审核 tab, a pending-recharge metric, and review actions in RechargeTable.
  • The existing /api/admin/billing/adjustments endpoint already supports direct positive/negative organization-wallet entries; positive entries are the correct current administrator top-up path and write immutable ledger entries.
  • The billing account configuration (/api/admin/billing/account) should remain because it contains the organizations future payment/account information, but it should not be tied to a review queue.
  • The recharge-request model is only referenced by the billing UI, billing GET/POST routes, the admin review route, billing store/service, schema table, docs, and billing tests. It can be removed from the active application surface without affecting task charging/refunding.
  • Product rule confirmed by the user: no manual review; administrator top-ups post directly, and a future user payment callback should post automatically after payment success.
  • The implementation now removes the active request/review UI and APIs, keeps the corporate account settings for future payment display, and exposes postOrganizationTopUp as the shared idempotent entry point for administrator credits and future payment-success callbacks.
  • Verification confirms there are no remaining active references to recharge requests, review endpoints, review UI, or pending-review CSS. The direct top-up test records a recharge ledger entry and preserves shared-wallet attribution.

Simplified billing price controls — 2026-08-11

  • The current PriceManagement UI exposes provider, capability, unit, standard price, markup, req key, legacy variant, quantity source, priority, seven condition fields, notes, custom rule creation, JSON condition editing, and enable/disable controls.
  • The catalog is already auto-seeded from provider reference prices and parameter variants; the requested admin responsibility is only the customer-facing markup multiplier.
  • The safe product boundary is read-only standard catalog data plus a single multiplier update action. The admin price PATCH route should reject edits to base price, conditions, quantity, provider/model identity, and enabled state.
  • Implemented the boundary: the billing center now renders a compact read-only price catalog, and each row exposes only 调整倍率; the former parameterized-rule creation form and structural controls are removed.
  • The admin price collection endpoint is now read-only, while the item PATCH endpoint accepts only markupMultiplier and rejects changes to platform standard costs, provider/model identity, parameters, quantity rules, priority, notes, and enabled state.
  • Verification passed with bunx tsc --noEmit, bunx vitest run (23 files / 86 tests), Node-backed production build, git diff --check, browser no-legacy-form/no-horizontal-overflow checks, and a clean local-server restart.

Parameterized billing catalog — 2026-08-11

  • User confirmed that every service/model must list its cost-affecting parameter tiers, while the super administrator only edits the multiplier for each tier.
  • Confirmed implementation direction: platform-owned parameter dimensions (for example quality, resolution, and reference-image count) are listed under each service; the quote engine combines the selected tiers instead of requiring a manually maintained Cartesian-product rule for every combination.
  • EvoLink GPT Image 2 exposes quality, resolution, size/aspect, count, and reference-image controls; its public page states that quality changes output-token cost and that high quality is approximately four times medium, so a single generic medium rule is insufficient.
  • Implemented parameterDimensions on the platform catalog. Each tier exposes a read-only standard factor/rate and an editable markup multiplier; combinations multiply the standard factors and use the highest selected markup multiplier once.
  • EvoLink now lists quality, resolution, aspect-ratio, and reference-image tiers. Existing video resolution rules are grouped under their service/model, while legacy single-rate services remain visible as a default tier.
  • The admin price PATCH route accepts either a legacy service multiplier or a dimension/tier multiplier target, while rejecting standard-cost and catalog-structure edits. The Supabase schema adds parameter_dimensions and the seed path backfills existing built-in rules without overwriting configured multipliers.
  • Verification passed: EvoLink medium/high quote tests, tier-multiplier update test, bunx tsc --noEmit, bunx vitest run (23 files / 88 tests), bun run build, browser service-card/dimension/overflow checks, git diff --check, and clean local-server health (/api/health 200).

Account directory redesign — 2026-08-11

  • The current /settings page renders AccountSecurityPanel, while /accounts is currently restricted to admin sessions and only renders administrator organization/member management.
  • Moving password change into /accounts without changing access would remove ordinary users' ability to change their own password. The safe product interpretation is to make /accounts authenticated-user accessible, render self-security for every user, and keep /api/admin/* and administrator management controls role-protected.
  • The existing account page is a dense vertical stack with a six-column create form that becomes visually cramped at the tested desktop viewport. The redesign will use a restrained two-column desktop composition: personal security as the stable account surface, administrator controls in a separate work area, and members in a dedicated list.
  • Existing API contracts and field names remain unchanged: /api/auth/password/change, /api/admin/accounts, /api/admin/accounts/password, and /api/admin/organizations continue to own their current operations.
  • Design read: modern minimalist enterprise utility surface, preserving the current green accent and light theme with DESIGN_VARIANCE 5, MOTION_INTENSITY 3, and VISUAL_DENSITY 4.
  • Final layout uses a stable desktop grid at the tested 1280px viewport with no horizontal document overflow. The page keeps the account directory as a real product surface rather than a marketing composition, so no generated imagery or decorative motion was added.

Account workspace correction — 2026-08-11

  • The first account redesign over-separated identity, security, admin actions, and member data. “账户信息” and “超级管理员” were two labels for the same current-user identity and should not be rendered as separate surfaces.
  • The corrected information architecture is a single workspace with a single identity summary, followed by security, administrator operations, and member directory sections. This removes visual fragmentation while preserving all actions and APIs.

Organization-only billing top-ups — 2026-08-12

  • The billing wallet is already organization-scoped, but the administrator adjustment API and UI still accepted an optional accountId for recharge/debit ledger entries.
  • The member balance table still exposed “归属上账”, which contradicts the confirmed rule that organization administrators and employees all use the same organization quota and that no personal top-up ownership exists.
  • New recharge and balance-adjustment entries will be normalized without accountId; generation charge/refund entries retain the acting account ID so member consumption reporting remains available. Existing historical ledger entries are preserved.
  • Implemented the invariant in the local billing store and Supabase wallet RPC; stale callers may still send accountId, but new recharge/adjustment rows are persisted without it.
  • Removed the member selector and “归属上账” action from the billing center, while retaining the member consumption read-only table.
  • Ledger display now hides historical personal labels for recharge/adjustment rows; task charge/refund rows can still show the executing member for consumption context.
  • Verification passed with focused billing tests (13/13), full Vitest suite (24 files / 92 tests), TypeScript, production build, and git diff --check for the reviewed tracked files.

Frontend encoding diagnosis — 2026-08-12

  • Source files inspected (app/layout.tsx, billing page/component, and global CSS) decode as UTF-8 without BOM, null bytes, or replacement characters.
  • Both 127.0.0.1:3000 and localhost:3000 return text/html; charset=utf-8; HTML contains correct Chinese strings and no common mojibake markers.
  • agent-browser rendered the login page and an isolated auth-disabled billing page with correct Chinese text; the billing screenshot also appears visually normal.
  • Two Next dev-server processes currently listen on the same workspace/port family (IPv4 and IPv6), so stale browser state or hitting different dev processes is a plausible local explanation. No encoding source change was made because the reported corruption is not reproducible yet.

Autofilled login submission — 2026-08-12

  • AuthLoginPanel disabled the submit button when React state values were empty, even though browser autofill could populate the visible inputs without firing the state onChange handler.
  • The form now has named, required controls and reads phone/password from new FormData(event.currentTarget) on submit; the button is only disabled by unavailable configuration or an active submission.
  • A browser regression check filled the displayed phone/password and confirmed a POST /api/auth/password request was sent after clicking the button.
  • Verification passed with the focused auth-panel tests (2/2), full Vitest suite (24 files / 92 tests), TypeScript, and production build.

Next development cache recovery — 2026-08-12

  • The reported runtime overlay was Cannot find module '/9971.js', with the require stack rooted in .next/server/webpack-runtime.js and the password route.
  • The active .next directory referenced 9971.js but the chunk was missing; two Next dev servers were also running against the same workspace/port family.
  • Stopped both stale dev servers, moved .next to .next.corrupt-20260812-1042 instead of deleting it, and started one clean next dev server on 127.0.0.1:3000.
  • The clean server compiled /create, /auth/login, /api/auth/password, and /api/health; browser verification reached the login API and received the expected 401 for intentionally invalid credentials, with no missing-module overlay.

Dev/production cache isolation — 2026-08-12

  • The follow-up ENOENT referenced a missing lucide-react vendor chunk under .next/server/vendor-chunks, showing that a later development restart was still sharing the production build directory.
  • next.config.ts now selects .next-dev for next dev and keeps .next for production builds; .next-dev/ is ignored by Git.
  • Moved the partially generated .next-dev directory to .next-dev.corrupt-20260812-1108 for recovery, then started exactly one clean development server.
  • Browser and HTTP verification passed: /auth/login rendered Chinese labels without a runtime overlay, /api/health returned 200, /create redirected to login as expected, and the regenerated Lucide vendor chunk exists in .next-dev.

Local super-admin credential recovery — 2026-08-12

  • The local account store contains the super-admin phone and password hash, not a recoverable plaintext password; the old password therefore could not be displayed safely.
  • After the user selected the super administrator, generated a new strong password and updated the account through /api/admin/accounts/password; the new password is not stored in source, documentation, or planning files.
  • Saved the credential under the project browser vault profile super-admin, then cleared cookies and verified the saved profile logs in successfully and reaches /create with super-admin navigation.

Inline generation cost estimate — 2026-08-11

  • The create page already debounced requests to /api/billing/quote with the same generation payload used on submit, but the preview lived beside the top-level submit button rather than beside the parameters that change the quote.
  • The cost card now surfaces the resolved amount, quantity, effective multiplier, and matched parameter tiers next to the parameter controls. It also distinguishes initial, loading, and unavailable states without inventing a client-side price.
  • The center workbench is narrow at 1280px because the template and task rails are both visible. A container query keeps the estimate beside the fields when there is room and stacks it inside the parameter area when the actual center column is too narrow; the document remains overflow-free.
  • The overcharge came from an invisible 2K provider default being combined with the 4× high-quality factor and the 1.5× platform multiplier: ¥0.34 × 4 × 4 × 1.5 = ¥8.16.
  • The confirmed product rule is now enforced in the provider client: EvoLink requests use fixed 1K resolution, and the billing catalog defaults to the same 1K tier. The old resolution environment setting/configuration field was removed to prevent request/quote drift.
  • The previous 1.5× multiplier produced ¥0.51/张 for medium and ¥2.04/张 for high quality; the platform-wide default is now 1.2×, so the same baseline becomes ¥0.41/张 and ¥1.64/张.
  • Ordinary users now see only 本次预计消耗额度 and the amount; platform multiplier, quantity, selected tiers, and helper/status copy are not rendered in the estimate card. Super-admin price controls remain available in the admin billing center.
  • Earlier browser verification confirmed the card resolved to ¥0.51 for standard and ¥2.04 for high under the previous 1.5× baseline; the current 1.2× values are covered by the live quote checks recorded below.

Unified default billing multiplier — 2026-08-11

  • The platform default markup is now 1.20× for all built-in provider rules and every seeded parameter tier, replacing the previous 1.50× default.
  • The local initialized billing state was synchronized to 1.20× for all service rules and parameter tiers; standard-cost values remain unchanged.
  • The admin adjustment capability remains available for intentional service/tier overrides; only the default baseline changed.

Cross-provider pricing audit — 2026-08-11

  • The shared quote formula is ceil(effective provider standard unit cost × native quantity × one platform markup). Parameter standard factors are applied to the provider baseline first; the platform multiplier is not applied once per parameter dimension.
  • Bailian wan2.7-image-pro currently matches the official Beijing price of ¥0.50 per generated image. Its current platform quote is ¥0.60/image at the default 1.20× multiplier.
  • Bailian wan2.7-i2v-2026-04-25 currently matches the official Beijing prices of ¥0.60/second at 720P and ¥1.00/second at 1080P. The current quantity source is video duration, so a 5-second quote is ¥3.60 and ¥6.00 respectively after 1.20× markup. No additional parameter multiplier is indicated by the official model page.
  • Ark doubao-seedance-2.0 currently stores the documented no-input-video, 16:9, 5-second examples as rounded per-second baselines: ¥0.46/second (480P), ¥0.99/second (720P), and ¥2.48/second (1080P). These produce current platform quotes of ¥2.76, ¥5.94, and ¥14.88 for five seconds at 1.20×.
  • The Ark source explicitly defines Seedance video billing as token price × token usage, with token usage affected by input-video duration, output duration, output dimensions, and frame rate; input video costs more than no-input video, and exact usage is returned in usage.completion_tokens. The current flat resolution-only Seedance catalog is therefore correct only as a no-input-video estimate, not as an exact quote for all accepted video/audio/material combinations.
  • The current Seedance UI/API accepts video and audio materials, but billing normalization records only duration and resolution for the Seedance rule. This is a pricing-model gap: input-video tasks can be underquoted, and exact ratio/frame-rate/material dimensions are not represented in the catalog.
  • The current Seedance per-second storage also introduces sample-rounding drift versus the official 5-second totals: 0.46×5=¥2.30 vs ¥2.31, 0.99×5=¥4.95 vs ¥4.97, and 2.48×5=¥12.40 vs ¥12.39. The difference is small but comes from rounding the sample total to a per-second fen price before multiplying.
  • Jimeng/Volcengine Visual jimeng_seedream46_cvtob has no stable public API price in the current official API document. The catalog's ¥0.20/image is explicitly an editable reference baseline derived from the public ¥200/1000-image activity package, not a guaranteed live API list price. The create UI forces single-image output, so the current one-image quantity is consistent with the active first-party flow.
  • No code or production pricing data was changed during this audit. A follow-up implementation needs a product decision for Seedance: keep conservative fixed reference estimates, or add provider-native token/usage reconciliation and a visible estimate policy for input-video jobs.

Seedance native usage settlement — 2026-08-12

  • The confirmed policy is: submit-time billing is a wallet reservation; successful Seedance tasks settle against the provider's usage.completion_tokens; a missing usage field leaves the reservation unchanged.
  • Seedance 2.0 token prices are represented as fen per million tokens: 480p/720p 4600 without input video and 2800 with input video; 1080p 5100/3100; 4K 2600/1600. The platform markup is applied once to the actual token cost.
  • The initial Seedance quote now uses the official token-usage estimate. If an input video is present without duration metadata, the reserve uses the supported 15-second upper bound so the organization is not under-reserved by default.
  • Query responses read both top-level and nested usage.completion_tokens/completionTokens shapes. Settlement uses job-settlement:{jobId} as its idempotency key, so worker retries cannot create a second adjustment.
  • A lower actual amount creates one refund difference entry; a higher actual amount creates one additional charge difference entry. The job billing snapshot is updated to the final amount and retains the reservation and provider usage details.
  • The Seedance catalog now includes the documented 4K reference variant, and the shared video resolution options accept 4k; fast-model resolution restrictions remain unchanged.

Unbound account quote preview — 2026-08-12

  • The empty estimate was caused by the current platform super-admin record having no organizationId. The quote route reused the strict organization check used for real task submission, returned an error, and the client rendered the missing quote as .
  • Quote-only helpers now allow an unbound session to resolve platform pricing. This does not change charging: submitImageJob/submitVideoJob still call the strict path, and an unbound real task cannot be submitted or charged.
  • Browser verification of the reported parameter combination (Image2, 9:16, 精细) now returns ¥1.64; direct service verification with one reference image resolves the same amount and parameter snapshot.

Quota guard and super-admin billing exemption — 2026-08-12

  • Confirmed product rule: ordinary platform accounts must not dispatch a real generation task when the shared organization balance cannot cover the frozen quote; the API should return the existing 402 insufficient-balance error so the create page can show it.
  • Confirmed product rule: super-admins still receive a provider-cost quote and successful jobs still write the calculated amount into usage records, but super-admin generation is quota-exempt. It must not require an organization, inspect wallet balance, create charge/refund ledger entries, or block on quota.
  • The existing submission path already charges before worker/provider dispatch and maps InsufficientBalanceError to HTTP 402. The missing pieces are role propagation, a persisted quota-exemption marker, and skipping wallet operations while retaining Seedance actual-usage settlement in the billing snapshot.

Billing UI alignment and multiplier dialog — 2026-08-11

  • Price-source links and explanatory notes now use a two-track metadata row: the source action keeps its intrinsic width while the note truncates in the remaining space, preventing baseline drift across service cards.
  • The create-page estimate card uses the compact 预估消耗 label and stays in the same parameter row at the tested desktop width; a live quote check showed EvoLink Image2 standard at ¥2.04 for one image.
  • 调整倍率 now opens an in-app modal with the current multiplier, standard cost, current customer price, projected customer price, range validation, keyboard focus, and Escape dismissal. The browser window.prompt path is removed.
  • Design polish followed the taste-skill direction: compact enterprise information hierarchy, shared alignment grid, restrained motion, and an in-context dialog instead of a browser-owned prompt.

2026-08-12 - Alibaba Cloud RDS PostgreSQL Refactor

Confirmed facts

  • The current production database path is not a direct PostgreSQL connection. data-store, account-store, and billing-store use @supabase/supabase-js over PostgREST and fall back to local JSON when Supabase variables are absent.
  • scripts/worker.mjs remains an HTTP poller and does not connect to the database; in the current architecture only Web pods require a database pool.
  • The existing SQL model uses PostgreSQL-compatible tables, JSONB/array/timestamp types, indexes, FOR UPDATE SKIP LOCKED, and two PL/pgSQL functions. Its application coupling is Supabase client semantics, not Supabase-only SQL namespaces.
  • Existing tests intentionally clear Supabase variables and exercise local JSON; there is no real PostgreSQL or RDS integration test and no live RDS credential is available in this workspace.
  • The existing /api/health route reports process/configuration state but does not probe database connectivity.

Decisions

  • Introduce explicit ZHINIAN_DATA_BACKEND=local|postgres. Production defaults must fail closed when the backend is missing or invalid; PostgreSQL mode must fail when DATABASE_URL is absent.
  • Preserve local JSON for development/tests, but never silently fall back from an explicitly selected PostgreSQL backend.
  • Centralize pg.Pool, TLS certificate loading, numeric environment validation, transactions, query execution, connection shutdown, and readiness in one server-only module.
  • Keep current store exports stable so routes, services, and components do not learn database transport details.
  • Preserve database-side claim_generation_jobs and billing_post_wallet_entry functions for concurrency correctness.
  • Use an independent versioned migration runner with advisory locking; ACK runs it as a one-shot Job before Web rollout, not as a per-pod init container.
  • Web receives database/session/provider/storage secrets; Worker receives only its internal token and internal Web URL.

Unverified assumptions and external boundaries

  • The target RDS PostgreSQL major version, instance connection limit, internal endpoint, TLS enforcement setting, CA bundle, database/user names, and network ACL/security-group rules are unknown and must be supplied/verified during deployment.
  • Without live credentials or an ACK context, code/build/schema checks can verify implementation shape but cannot prove real RDS connectivity, migration success, or cluster rollout.

Implementation and audit findings

  • Direct PostgreSQL now uses a lazy process-level pg.Pool, explicit backend selection, validated integer limits, verified CA TLS, parameterized queries, a single-client transaction helper, and schema/privilege-aware readiness. Connection-string SSL query parameters are rejected so they cannot silently override the mounted CA policy.
  • The three stores and both account scripts no longer use Supabase/PostgREST. Local JSON remains explicit for development/tests; production without a valid backend or PostgreSQL URL fails closed.
  • The migration runner serializes with a session advisory lock, verifies immutable checksums, wraps each version in a transaction, provisions exact current-object grants for a constrained application role, revokes public function execution, and deliberately avoids blanket/default future-object grants.
  • Read-only audits found and implementation fixed four concurrency/semantic defects: concurrent duplicate wallet idempotency, concurrent failed-login count loss, PostgreSQL billing-update 23505 mapping, and JSONB condition-array canonicalization.
  • ACK templates now keep Web at one replica until storage is externalized, separate Web/Worker/DB secrets, isolate the public internal-worker prefix through a selectorless Service, mount the RDS CA, disable service-account token mounts, and split liveness from database/schema readiness.
  • The first final Sol review returned FAIL and uncovered additional production risks. All reported code findings were addressed before re-review: wallet idempotency is organization-scoped and binds immutable account/job/kind/amount/currency fields while treating description/metadata as mutable audit detail; destructive usage deduplication became a fail-safe preflight; app grants are explicit with no future-object defaults; readiness covers all runtime tables; password changes use a row-lock transaction; server-only imports are enforced; and npm/pnpm resolve the same pinned Next/React toolchain.
  • Main-thread final verification passed 31 test files / 119 tests, TypeScript, the Next production build, all 8 ACK manifest assertions, frozen lockfile validation, script syntax, diff hygiene, and documentation drift. The final independent Sol review returned PASS; live RDS/ACK/Docker verification remains explicitly outside the available environment.