- nav: the menu's onClick={setOpen(false)} on the Sign-out submit button
was racing the form POST — React unmounted the form before the request
flushed, so logout silently no-op'd. Drop the onClick; the Server
Action's redirect to /cm-auth tears the menu down naturally.
- nav: drop the 'Passkey settings' link (passkey UI is gone).
- Delete web/app/cm-passkeys/. The WebAuthn Server Actions in
auth-actions.ts are unreachable now (hasPasskeysForLogin always returns
false in practice — no enrollment path), so the 'Sign in with passkey'
button on /cm-auth never renders. The action handlers stay in case we
reinstate enrollment later; they're dead code but harmless.
- auth-form: add an eye-toggle button on the password field that flips
type=password ↔ text. tabIndex=-1 so Tab still goes input → submit
without stopping at the toggle. Right-padded the input (pr-10) so the
glyph doesn't overlap typed characters.
End-state: a single web service (Next.js dashboard) per deployment, no
side-by-side Flask UI. The image name 'cm-web' now points at the Next.js
build; the legacy 'cm-web-next' tag is no longer published.
Changes:
- Delete app/cm_web_view.py and the Flask docker/web/Dockerfile.
- Rename docker/web-next/ → docker/web/ (Next.js Dockerfile takes the
cm-web slot).
- docker-compose.yml: drop the web-view service. Rename web-next → web,
container ${CM_DEPLOY_NAME}-web-next → ${CM_DEPLOY_NAME}-web, image
cm-web-next → cm-web, named volume web-next-auth-data → web-auth-data.
transfer-bot's depends_on no longer references web-view (vestigial
startup ordering, never a runtime dependency).
- docker-compose.override.yml: same rename, dockerfile path updated.
- envs: drop CM_WEB_NEXT_HOST_PORT. Repurpose CM_WEB_HOST_PORT for the
Next.js port (8010 dev, 8011 rex, 8012 siong) — same numeric values
formerly held by CM_WEB_NEXT_HOST_PORT, so aaPanel routes don't move.
- scripts/dev.sh: drops web-view + web-next from up/reset-db/logs;
--remove-orphans still cleans up legacy containers from before cutover.
- scripts/publish.sh: drop the cm-web-next build target.
- tests/test_debug_enabled.py: drop app.cm_web_view from the helper
matrix (cm_api is now the only Flask entrypoint with _debug_enabled).
- AGENTS.md / README.md / docs/aapanel-hardening.md: rewrite Flask-era
references; add migration steps for existing stacks; update aaPanel
port references (8000/8001/8005 → 8010/8011/8012).
- .gitignore: add .env, .venv/, .playwright-mcp/, node_modules/, .next/
so 'git add -A' can't accidentally stage secrets or build artifacts.
Operator action required to upgrade an existing deployment:
1. .env: drop CM_WEB_NEXT_HOST_PORT line. Set CM_WEB_HOST_PORT to
what CM_WEB_NEXT_HOST_PORT was. Make sure CM_AUTH_SECRET is set.
2. aaPanel: if proxy_pass pointed at the legacy Flask port
(8000/8001/8005), switch it to the new one (8010/8011/8012).
3. Pull the new cm-web image (Next.js) and redeploy the stack. The
old ${CM_DEPLOY_NAME}-web-view and ${CM_DEPLOY_NAME}-web-next
containers will be replaced by a single ${CM_DEPLOY_NAME}-web.
Verified locally: docker-compose YAML parses; transfer-bot runtime is
unchanged (only depends_on tidied); 38-test python suite passes.
Wraps openssl rand -hex 32 (with /dev/urandom fallback) so operators don't
have to remember the incantation. Defaults to printing the secret;
--write [path] sets/replaces CM_AUTH_SECRET in the target .env (./.env by
default) and prints the restart command.
In @simplewebauthn/server v11 the JSON response and transport types are
no longer re-exported from the server package — they live in the sibling
@simplewebauthn/types package. Adds the dep and switches the imports.
Previously the session cookie used Secure=NODE_ENV==='production', and the
dev override still runs the standalone build with NODE_ENV=production, so
the cookie was unreachable from phone-on-LAN testing over HTTP. Switching
to CM_DEBUG lets dev (CM_DEBUG=true) drop the Secure flag while keeping
prod (CM_DEBUG=false) safe.
Also wires CM_AGENT_ID/CM_AGENT_PASSWORD/CM_DEBUG into the web-next
service env block so the login Server Action can compare against them.
Earlier change made the badge the editable trigger and demoted it
into the body's Status row. That separated status from the row
identifier on mobile, which read as 'where is this status from?'.
Move the EditableCell-with-StatusBadge back into the card header,
right after the username, and drop the body Status row entirely.
Mobile now matches desktop's information density: identifier +
status badge inline, edit via badge click.
Avoids the well-known /login path that scanners hit by default.
The cm- prefix matches the rest of the project's namespacing
(cm-web-next, cm-api, etc.) and isn't on standard scanner wordlists.
Settings page moves to flat /cm-passkeys (was /settings/passkeys)
to drop the simple 'settings' word — same scanner-noise reasoning.
File paths follow: web/app/cm-auth/, web/app/cm-passkeys/.
The status column was rendering both a StatusBadge and a separate
EditableCell next to it, so 'done' showed twice in the same cell.
Adds a renderView prop to EditableCell so callers can override the
view-mode display; status now uses the badge as its visual, click-
to-edit behavior intact. Mobile card header drops its standalone
badge for the same reason — the body's Status row now shows the
badge inline.
Native <dialog> in iOS Safari (and a few other browsers) doesn't
prevent the page underneath from scrolling when the user scrolls
inside the dialog or near its edges. Save and restore body overflow
on open/close so the background stays put. Stays correct for stacked
dialogs because we save the previous value rather than blanket-reset
to ''.
api-server is internal-only after C5 (no host port in prod compose),
so the permissive 'CORS(app)' default never fires in normal operation.
Removing it eliminates a stale '*' Access-Control-Allow-Origin that
would become attack surface if a host port were ever accidentally
re-exposed.
Server-side fetches from web-view (legacy Flask) and web-next
(Next.js RSC) don't trigger CORS — that's a browser-only mechanism.
flask_cors stays in requirements.txt because cm_web_view.py still
imports it; both get removed in B4 when the legacy web-view retires.
Adds viewportFit: 'cover' so the PWA can draw under the notch /
Dynamic Island when installed. Nav and Toast read env(safe-area-inset-*)
to keep their content out of the hardware cutouts (no-op on browsers
without a notch — env() resolves to 0).
Replaces autoFocus on the first field of CreateAccountDialog and
CreateUserDialog with a useEffect that only focuses on pointer devices
(matchMedia '(hover: hover) and (pointer: fine)'). Phones no longer
get the soft keyboard popping the instant a dialog opens.
Adds a small top-centered <Toast> that fires only when the Server
Action returns { ok: true } (i.e., the DB write actually succeeded).
Auto-dismisses after 3s.
Wires both create dialogs (CreateAccountDialog, CreateUserDialog) with
an onSuccess callback that the table parent uses to push the toast,
and the delete confirm-flow does the same. Inline-edit success stays
quiet (no toast) — only add/delete trigger it, per the requested
scope.
api-server gets /create-acc-data and /create-user-data POST routes
that INSERT into the respective tables with required-field validation.
Frontend adds an 'Add' button next to Refresh in each table head;
opens a native <dialog> form with all fields. Inputs use 16px font on
phone (sm:text-[13px] desktop) so iOS doesn't auto-zoom.
A small form-dialog-shell helper centralizes the modal chrome,
field label, and input class so create-account-dialog and
create-user-dialog stay focused on their fields and validation.
Adds × delete button per row in both tables (desktop column +
mobile card header). Click → native <dialog> confirm modal with
Esc/backdrop-cancel, destructive red button, error inline.
Wires deleteAccount/deleteUser Server Actions calling the new
api-server routes; revalidatePath refreshes the list on success.
EditableCell input switches to text-base (16px) on phone (sm:text-[13px]
above 640px), preventing iOS Safari auto-zoom-on-focus that was
shifting the layout when the soft keyboard appeared.
Drop the brutalist hazard-tape vocabulary in favor of refined modern
SaaS: white cards on zinc-50, soft ring-1 zinc-200 borders (no hard
2px black), rounded-full pills, sans for chrome + mono for tabular
data, emerald replacing yellow as the saturated accent. Theme color
shifts to zinc-900 with an emerald dot on the icon.
Long URLs in the link column would overflow on mobile because
truncate + inline-flex without min-w-0 expanded the cell beyond the
card width. Switch to flex+items-start, min-w-0 on the value span,
break-all so unbreakable strings wrap. Edit hint stays pinned right
with shrink-0.
The Route Handler proxy and hash mapping are gone. Browser never
hits a JSON endpoint: data reads happen in React Server Components
fetching api-server:3000 server-side; mutations (B2) will use
Next.js Server Actions. Zero public API surface to scrape or
enumerate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>