From 869e2bb75b9a6dd98b3d7cf98a22ac979b30a463 Mon Sep 17 00:00:00 2001 From: liangxu Date: Sat, 11 Jul 2026 01:02:12 +0800 Subject: [PATCH] chore: update planning notes Co-Authored-By: Claude Opus 4.8 (1M context) --- findings.md | 28 ++++++++++++++++++++++++++++ progress.md | 35 +++++++++++++++++++++++++++++++++++ task_plan.md | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/findings.md b/findings.md index fa90e9f..428b9c8 100644 --- a/findings.md +++ b/findings.md @@ -37,3 +37,31 @@ - Direct image mode currently sends the raw user prompt to image generation, bypassing the creative planner. Brand context therefore must also be added to the direct/fallback image prompt builders, not only long-term memory and `projectBriefContext`. - Asynchronous create/follow-up jobs reload projects from persistence, so Brand Kit context must be re-resolved inside those job execution paths; hydrating only the initial HTTP turn would be insufficient. - Uploaded Brand Kit logos, cover images, and reference images are resolved server-side as ephemeral project inputs. Image generation appends them after user-supplied references so existing inline reference numbering remains stable. + +## Device Management Request (2026-07-11) +- The requested visual is a restrained account settings page headed `设备管理`, with a safety-limit explanation, a bulk removal action, and a table of active devices. +- The repository already contains `list_account_devices_logic.go`, `remove_account_devices_logic.go`, and an `AccountManagementDialog`, so the first implementation step is to verify and complete the existing vertical slice instead of creating a parallel settings system. +- The target interaction must cover loading, API failure, no secondary devices, current-device labeling, disabled destructive actions, confirmation, and responsive behavior. +- The account dialog already renders a device section and calls `authGateway.devices()` / `removeDevices()`, but it silently fabricates a current-device row whenever the API is empty or fails. That masks production errors and makes destructive-action state unreliable. +- The existing removal handler has no pending/error guard around the async request, so repeated confirmation clicks or a network failure can produce duplicate calls or an unhandled rejection. +- Existing localized copy says bulk removal invalidates the current session, while the visible button is disabled when only the current device remains. Backend behavior must be treated as the source of truth and the copy aligned to it. +- The server device service is currently a stub: `ListDevices` always synthesizes one unknown desktop row and both `RemoveOtherDevices` and `Logout` return zero. There is no persistent session/device registry. +- Access and refresh tokens currently identify only the user and token type. Production-grade selective revocation therefore requires a random session ID claim plus a server-side session record; user-only stateless tokens cannot distinguish the current device from other logins. +- The desired bulk action is already named `RemoveOtherDevices` and the UI disables it when only the current session exists, so the consistent contract is: preserve the active session, revoke every other session, and update confirmation copy accordingly. +- The authentication store supports users and verification codes only. A complete implementation must add session persistence to both memory and PostgreSQL stores, bind token validation to active session state, and capture request/device metadata without putting HTTP concerns into business logic. +- Selected architecture: signed access/refresh tokens receive an opaque random `sid`; active sessions are persisted with user, device, creation, expiry, last-seen, and revocation timestamps; every bearer authentication checks the session registry. +- Backward compatibility: an otherwise-valid legacy access token without `sid` is mapped to a stable digest-derived session only after confirming that its subject is an existing user. This avoids a forced logout during rollout and prevents signed state tokens from becoming user sessions. +- Middleware will attach sanitized request metadata for public login routes and authenticated requests, then attach the resolved session ID to request context. Business logic remains HTTP-agnostic. +- Last-seen writes will be throttled and active device rows sorted current-first, then most recently seen. Device metadata is informational only. +- PostgreSQL uses the repository's existing idempotent embedded schema; memory and PostgreSQL stores will implement the same atomic revoke-others contract. +- `github.com/mileusna/useragent` is now the selected parser. It exposes normalized browser, OS, device, desktop/mobile/tablet, and bot fields while keeping authorization independent from user-agent data. +- The Next.js API proxy forwards incoming headers after removing only hop-by-hop headers, so browser UA and Client Hint metadata reach the Go middleware without a new client contract. +- The current device table is rendered as three independent columns rather than semantic rows. This makes loading/error/empty states awkward, can misalign cells, and forces horizontal scrolling on mobile. +- The dialog already has restrained neutral styling consistent with the supplied reference. The right approach is to preserve that language while replacing only the device slice with a row grid, explicit async state machine, accessible status text, and mobile stacking. +- Current helper text fabricates an extra empty separator in the device string. The replacement will expose normalized primary metadata and a quieter client/last-seen line instead of leaking placeholder delimiters. +- Follow-up requirement: the desktop and mobile session counts must be configurable. The backend config will be the source of truth, `GET /api/account/devices` will return the effective limits, and localized UI copy will interpolate those values. +- Bug confirmation: `TestDesktopDeviceLimitRevokesOldestSession` fails because `upsertAndSign` creates sessions without consulting `DeviceLimits`; the values currently affect only API/UI presentation. +- Enforcement policy: limits apply independently to normalized desktop and mobile sessions. A new login succeeds and atomically revokes the oldest same-type session, so the latest login wins and active sessions never exceed the configured count. +- Existing sessions created before enforcement are reconciled when the authenticated device list is read, preserving the current session first and then the most recently created sessions. +- Follow-up display requirement: `lastSeenAt` must be a timezone-neutral instant over the API. The backend will emit RFC3339 UTC and the browser will format it with `Intl.DateTimeFormat` using its local timezone and the selected UI locale. +- No configured `mcp-zero` callable tool is available in this session, so API validation/generation will use the documented `goctl` fallback after the `.api` spec is updated. diff --git a/progress.md b/progress.md index 5fce9cb..c83d5a9 100644 --- a/progress.md +++ b/progress.md @@ -35,3 +35,38 @@ | `go build ./...` | build succeeds | pass | | `npx tsc -b` | typecheck succeeds | pass | | `npm run build` | Next.js and Vite production builds succeed; existing large-chunk warning only | pass | + +## Session: 2026-07-11 + +### Phase 7: Device Management Discovery and Design +- **Status:** in_progress +- Loaded repository instructions, go-zero workflow rules, frontend design guidance, and the existing planning state. +- Confirmed a clean worktree before starting the device management task. +- Located an existing account-management component and server device list/removal logic for focused inspection. +- Confirmed the current device endpoints are placeholder implementations over stateless user-only tokens; selective revocation is not yet real. +- Completed the session-registry design, including token `sid`, legacy-token migration, request metadata context, throttled last-seen updates, expiry filtering, and atomic revoke semantics. + +### Phase 8: Device Management Implementation +- **Status:** in_progress +- Added the maintained `github.com/mileusna/useragent` dependency for normalized, display-only device metadata. +- Implemented token `sid` claims, session context, UA normalization, memory/PostgreSQL session persistence, active-session authentication, last-seen throttling, listing, revoke-others, and logout revocation. +- First targeted test run compiled successfully; the legacy placeholder test failed because removal now correctly requires a current authenticated session context. The test is being upgraded to the production contract. +- Replaced the placeholder test with multi-device lifecycle coverage and added UA parsing, legacy migration, revocation, logout, and middleware metadata/session-context tests. +- Targeted Go tests now pass: `go test ./internal/modules/auth ./internal/handler`. +- Rebuilt the account device section with explicit async states, semantic rows, normalized metadata, responsive stacking, duplicate-submit protection, optimistic post-removal reconciliation, and aligned Chinese/English copy. +- Frontend TypeScript validation passes: `npx tsc -b`. +- Added the follow-up requirement for server-configurable desktop/mobile web-session limits; implementation will extend the API response spec before regeneration. +- Updated and validated `img_infinite_canvas.api`, then regenerated go-zero types with the documented `goctl` fallback; the device list response now includes `limits.desktop` and `limits.mobile`. +- Added `Auth.DeviceLimits.Desktop/Mobile` defaults to local and deployment config, wired them through the auth service, and replaced hard-coded UI counts with API-driven interpolation. +- Documented the configuration and effective API policy in `server/README.md` and `server/API.md`. +- Focused Go tests and frontend TypeScript validation pass after the configurable-limit change. +- Full verification passes: `go mod tidy`, API validation, `go test ./...`, `go build ./...`, and `npm run build` (only the existing Vite large-chunk warning remains). +- Initial alternate-port preview starts exposed two environment constraints: go-zero requires a config filename extension and Next permits one dev server per repo. Switched to a `.yaml` stdin symlink for the backend and the production Next server for the second frontend port. +- The isolated preview is live at `http://localhost:5174` with the new backend on `http://localhost:8889`; the frontend page, auth-options proxy, and protected-device 401 boundary respond correctly. +- The in-app browser runtime has no available browser backend in this environment, so authenticated screenshot inspection could not be completed there. +- Race-enabled auth/middleware tests pass, frontend typecheck and diff checks pass, and the preview proxy still enforces a 401 on unauthenticated device access. +- Reproduced the reported limit bug with a deterministic failing service test: two desktop tokens remained valid with `Desktop: 1`. +- Implemented atomic same-type enforcement in memory and PostgreSQL stores; new logins keep the newest session and revoke oldest overflow sessions. +- Added device-list reconciliation for sessions created before enforcement and verified current-session preservation. +- Changed the device API timestamp contract to RFC3339 UTC and formatted it with the browser's local timezone via `Intl.DateTimeFormat`. +- Regression tests now pass for configured desktop limit 1, pre-enforcement reconciliation, concurrent PostgreSQL logins, and timezone-neutral API timestamps. diff --git a/task_plan.md b/task_plan.md index 979889e..0e39bec 100644 --- a/task_plan.md +++ b/task_plan.md @@ -4,7 +4,7 @@ Persist Brand Kits by authenticated user, bind one optional Brand Kit to each project/canvas, expose a canvas-header selector, and make every Agent Chat turn and newly created thread use the selected kit as authoritative creative context. ## Current Phase -Phase 5 +Phase 7 ## Phases @@ -45,6 +45,29 @@ Phase 5 - [ ] Provide preview URL and concise implementation notes - **Status:** pending +### Phase 7: Device Management Discovery and Design +- [x] Trace the existing account dialog, authentication gateway, and device API behavior +- [x] Define production-grade loading, empty, error, current-device, and destructive-action states +- **Status:** complete + +### Phase 8: Device Management Implementation +- [ ] Implement the device management view and responsive styling in the existing account surface +- [ ] Wire list/remove-all behavior to the authenticated device API without changing unrelated account flows +- [ ] Make desktop/mobile session limits configuration-driven and return the effective policy from the device API +- [ ] Enforce each configured device-type limit atomically and render last-active timestamps in the browser timezone +- [ ] Add focused backend or frontend coverage where the repository supports it +- **Status:** in_progress + +### Phase 9: Device Management Verification +- [ ] Run focused tests, frontend typecheck/build, and Go verification if backend code changes +- [ ] Inspect the authenticated desktop and mobile UI in a real browser +- **Status:** pending + +### Phase 10: Device Management Delivery +- [ ] Review scope and final diff +- [ ] Provide the working preview URL and concise implementation notes +- **Status:** pending + ## Invariants - Brand Kits are owned and queried only through the authenticated user ID from request context. - A project may reference zero or one Brand Kit owned by the same user. @@ -52,6 +75,14 @@ Phase 5 - Server-side Agent Chat context is derived from the persisted project binding, never trusted from a client-supplied prompt. - Home may request a Brand Kit ID only from the authenticated user's list; the server revalidates ownership before binding it. - Deleting a Brand Kit clears project bindings to it. +- Device sessions are always scoped from the authenticated server-side user identity. +- Bulk device removal preserves the current session unless the product contract explicitly requires otherwise. +- New access and refresh tokens carry an opaque random session ID; the server validates that session before accepting the bearer token. +- Legacy access tokens without a session ID are migrated once from a stable token digest after user validation, preserving existing signed-in users. +- Revoked and expired sessions never authenticate or appear in the active-device list. +- Device metadata is derived from request headers for display only and never used as an authorization signal. +- Device timestamps cross the API boundary as RFC3339 UTC instants and are localized only in the browser's timezone. +- Desktop and mobile web-session limits come from server configuration; the API returns the effective values and the UI never hard-codes them. ## Errors Encountered | Error | Attempt | Resolution | @@ -64,3 +95,10 @@ Phase 5 | One patch to remove a temporary `err = nil` line used the wrong surrounding field name | 1 | Reapplied the patch against the exact current lines; no code change was lost. | | Workspace import patch assumed a nonexistent `BrandMark` import | 1 | Located the actual import block and applied the selector change against current source. | | First i18n patch used an inexact Chinese source string | 1 | Re-read the locale file and patched the exact key/value context. | +| Existing account-device test called removal without an authenticated session context | 1 | Update the test to resolve the issued bearer identity and assert the new current-session contract. | +| Preview backend rejected `/dev/stdin` because go-zero infers config format from the filename extension | 1 | Use a `.yaml` symlink to stdin so the transformed preview config retains a recognized extension without creating a repository config file. | +| A second Next development server refused to start because the existing repo-wide dev lock is active | 1 | Use the already-built Next production server on the alternate preview port. | +| BSD `sed` did not support the GNU `0,/pattern/` address used for the preview port substitution | 1 | Replace it with a portable anchored substitution; the backend now listens on 8889. | +| The in-app browser runtime reported no available browser backends | 1 | Keep the verified local preview running and report the browser-only visual inspection gap; do not substitute an unrelated browser surface against the browser skill rules. | +| Escaping the `psql \d` meta-command through Docker produced an invalid command | 1 | Query `information_schema.columns` and `pg_indexes` with regular SQL instead. | +| Desktop limit regression test observed two valid desktop sessions when the configured limit was 1 | 1 | Confirmed the limit was presentation-only; enforce it atomically during session creation and reconcile pre-fix sessions during device listing. |