Commit graph

2724 commits

Author SHA1 Message Date
Fini fda7e26e36 feat(i18n): add this round's panel keys across all fifteen locales
Thinking modes, slides export, repair-ledger lines, style import file
picking, template scenes and forty-six new template titles/summaries
land together so every later commit's key lookups resolve. Catalog
integrity count moves 1526 -> 1640.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 02:22:01 +08:00
Kayshen-X ace4c7257e feat(web): fetch preview assets at runtime instead of embedding them
The preview JPEGs (~2.4 MiB, already compressed so gzip passed them
straight through) leave the wasm data segment: a platform-free asset
registry in op-editor-core tracks per-route Absent/Pending/Ready/Failed
with single-flight and install-once semantics, the browser half fetches
over ArrayBuffer XHR with managed-mode headers and a slot-wrapped
callback so no synchronous failure can strand a route in Pending, and
paint sites fall back to the existing placeholder when bytes are not
(yet) there. Native keeps include_bytes verbatim. The staging script
copies the asset dirs into pkg/assets/ — under /pkg/ because the hub
frontend owns /assets/ — and the gate, CI workflow, and web image all
run it and assert the layout. Bundle: 7.13 → 5.10 MiB gzip, so the
tripwire returns to 6 MiB (85% occupancy); the sdk bundle keeps its own
8 MiB pending a real measurement.

Also closes the final review test gaps: the owner-session fixture now
returns a must-use lane guard (a dropped receiver made the saturated
lane read as Disconnected, not Full) and seeds the daemon's baseline
document so the hash check exercises the real path, and the closed
write barrier has a direct multi-page active-page regression test.
2026-08-09 01:37:06 +08:00
Kayshen-X f7afc554f6 fix(desktop): restore windows deps and gate mac-only test imports
Adding the SIGTERM handler had introduced the cfg(unix) dependency
section above five cross-platform dependencies, silently making zip,
memmap2, glam, dirs, and skia-safe unix-only — invisible on mac/linux
gates, fatal on the windows runner. Only libc belongs in that section.
The complex-script test file's shared imports and constant now carry
the same macOS gate as every test in it, so linux clippy stops flagging
them as dead.
2026-08-09 00:54:48 +08:00
Kayshen-X af15f6cdee feat(editor): canvas toast banner surfaces the online recovery notice
A single-slot, self-expiring toast (8s, read-time expiry so the state
stays a pure function of the clock) renders top-centre of the canvas —
the one band with a single occupant, stacking under the align toolbar
and clamped clear of the tool column. Paint sits above panels and the
diagnostics notice, below the missing-font modal, with the press tier
mirrored in both hosts through one shared flow arm; the deadline folds
into the shared frame bookkeeping so each host wakes exactly once at
expiry. The online conflict auto-accept now shows the existing
localEditPreserved string (all 15 locales) naming Undo as the way
back; collab sessions keep their panel notice instead of saying it
twice, and an identity switch dismisses the previous account's toast.
2026-08-09 00:47:05 +08:00
Kayshen-X 379642aab5 fix(web): admit metadata writes, prove the thumbnail branches, close AI gaps
- the AI snapshot path's editor-metadata writes (authored-geometry
  flag, active page) now take the same write admission as document
  content — they serialise into the tenant snapshot, so a post-close
  write could still lose acked state; a closed barrier skips them
  while the chat reply streams on
- op-collab-host gains a cfg-gated test-support fixture (activated
  owner session, saturated command lane) so op-host-services drives
  the real begin→install→finish path: Rejected restores the thumbnail
  snapshot, a standalone-fallback Failed keeps the new thumbnails —
  both mutation-checked, replacing two assertions that never reached
  the branch; a test-only registry lock stops the process-global
  thumbnail table from crossing test isolation
- the three remaining AI closed-barrier paths (starter clear, modify
  reply, design doc sink) are pinned by tests, and the identity-reset
  comment no longer claims an epoch re-tag that would itself be the
  bug
2026-08-08 23:38:40 +08:00
Kayshen-X 1bca4c1f49 fix(desktop): update collab smoke to the PeerIdentityPolicy admission API
The pairing-code hardening changed the admission exchange's
expected-subject parameter into PeerIdentityPolicy, but the smoke
harness only builds under --features test-issuer, which no workspace
gate compiles — CI's two-process collaboration step was the first
thing to notice. All four call sites now pass the SameAccount policy;
the full 8-scenario smoke run passes again.
2026-08-08 22:59:05 +08:00
Kayshen-X 70e955ebc2 fix(sdk): raise the sdk bundle tripwire to the shared 8 MiB 2026-08-08 22:37:17 +08:00
Kayshen-X 055db1895f fix(web): request-time epoch snapshots, AI write admission, drain without ceiling
- credential-sync callbacks compare the epoch captured when their
  request was issued instead of a shared cell the reset itself
  rewrote, so a stale callback can no longer pass as current; the
  first identified partition reload also restarts the sync flow,
  clearing the in-flight latch a mount-time policy fetch left wedged
- AI standard-turn commits (snapshot replace, starter clear, modify
  apply, doc sink) each take a write pass at the commit instant — the
  streamed conversation never holds the barrier — and a closed
  barrier leaves the document untouched while the reply still
  streams; /api/ai/stream was verified to have no commit points
- the shutdown write drain has no ceiling: flush runs only once no
  write is in flight, with escalating logs and stop_grace_period
  documented as the one true upper bound
- LocalEditOutcome::Failed reports whether the document rolled back,
  and the thumbnail snapshot is restored only when it did — the
  delivery-failure fallback keeps the edited document, so restoring
  old thumbnails there described a document that no longer existed
2026-08-08 22:35:42 +08:00
Kayshen-X 1030f5b131 fix(web): unbreak linux CI — keepalive EINVAL and the bundle tripwire
The idle-heartbeat test failed deterministically on linux (not the
timing flake it was first read as): TCP_KEEPIDLE/TCP_KEEPINTVL have
whole-second granularity there, so the test config's 50ms heartbeat
truncated to zero and setsockopt returned EINVAL on both ends of the
handshake — a real runtime bug for any sub-second heartbeat config,
fixed by clamping only the kernel keepalive cadence to >= 1s.

The 6 MiB gzip tripwire was set when the bundle measured ~4.5 MiB;
feature growth since (collab, prompt center, templates) added ~4.5 MiB
of embedded product assets, ~2 MiB of it pre-compressed JPEG that gzip
passes through. Reset the tripwire to 8 MiB and document that it is a
regression guard, not a budget, with the asset-split TODO that brings
the bundle back down.
2026-08-08 22:10:13 +08:00
Kayshen-X f3ea6e8c99 fix(web): make the identity reset claims real and cover /mcp in the write barrier
Re-audit round: an aborted edit script had silently dropped half a
prior fix, so each item here is grep-verified in the report trail.

- identity reset now actually drains the credential sync queue and
  epoch-tags in-flight callbacks so account A's completing upload
  cannot become account B's state; an empty partition resets every
  account-scoped setting (locale, recents, agent settings, theme)
  instead of inheriting the previous account's; rebuilt persistence
  baselines go through the CredentialLoad seams so settings keep
  saving and unsupported snapshots stay fail-closed after a switch
- mutation admission is one helper shared by the REST push and /mcp:
  write tools take a WritePass (read tools stay free) and refuse with
  a proper tools/call error once shutdown closes the barrier; the
  write drain escalates to a 60s hard cap with blocked-writer logging
  instead of flushing past active writers at 10s
- a runtime-rejected ingest restores the thumbnail registry snapshot
  captured before install (vendor/jian gains the restore_snapshot
  counterpart), so a rejected document's thumbnails no longer stay
  active locally
2026-08-08 21:35:27 +08:00
Kayshen-X 62d944710b fix(web): credential partition hygiene, shutdown write barrier, seed lifecycle
- an empty settings partition now clears the previous account's
  in-memory credentials instead of keeping them; identity reset also
  drains the credential upload queue and rebuilds both persistence
  fingerprints after the partition load (before it would re-measure
  the old account's state as a pending write); partitions key on the
  stable subject the daemon now advertises, so a handle rename cannot
  move or merge accounts
- online shutdown closes a write barrier before draining: late writes
  get 503 shutting-down instead of an ack that will not persist, and
  the flush logs its duration so stop_grace_period can be sized
- a conflict auto-accept that cannot stash skips the accept and
  retries rather than overwriting unpushed work without a backup
- the collab gate runs before the push surrenders its document, so
  refused pushes release their thumbnail seed through Drop; the
  runtime-reject path discards explicitly, and the seed tests now use
  the same allocation the parse produced instead of a clone that
  could never match
2026-08-08 20:44:32 +08:00
Kayshen-X 1507911199 fix(web): wire the review remedies end to end
The re-review found several remedies that existed but were not
reachable; this closes them:

- online answers /api/auth/status with the verified identity
  projection (login/logout stay 404), so the account-switch epoch
  actually fires; first identification reloads the account's settings
  partition, identity reset also tears down the id allocator, and an
  Active projection whose namespace disagrees rebuilds it
- finish_local_edit reports Committed/NoChange/Rejected/Failed and a
  real runtime rejection answers 409 version-conflict with the
  authoritative version — the one shape the browser's recovery parses
- the REST scope gate runs ahead of every dispatch path including
  share, AI, figma, and SSE (/mcp keeps its stronger per-tool check)
- shutdown drains connections (bounded) before the flush, the tenant
  store probes create/write/rename/delete at startup and fails closed
  naming the container uid, and worker-thread spawn failures abort
  startup instead of silently never persisting
- ACL flushes hold the same guard as updates, and the 257th grant is
  refused rather than silently truncated on the next write
- the conflict stash lost its call site in an earlier split and is
  invoked again before accept; SSE subscribe prunes dead slots; an
  uninstalled document push drops its pending thumbnail seed
2026-08-08 19:57:08 +08:00
Kayshen-X 6e036e1528 fix(web): recovery affordance, tenant-aware URLs, and restore isolation for online
Closes the remaining review findings:

- an online conflict auto-accept stashes the overwritten document
  (single slot, cleared on identity change) and tells the user the
  remote version was applied and Undo restores their work — the apply
  was already undoable via the initialized() latch, now pinned by
  tests on both sides of the seam and named in all 15 locales
- every wasm daemon request goes through one tenant-aware URL builder
  (idempotent stamping at construction and dispatch), fixing the AI
  transport, model catalog, and image panel requests that dropped the
  ?tenant= parameter on shared pages
- tenant restore and eviction persistence no longer touch the
  process-global thumbnail registry, so two restored accounts cannot
  capture or overwrite each other's image state
- document pushes parse and validate before the state lock; only the
  baseVersion re-check, gate, and infallible install run under it
2026-08-08 18:25:00 +08:00
Kayshen-X d909578ddb fix(web): close the review's high-severity online gaps
- SSE subscribers hold a single latest-value slot (weak-pruned) so a
  stalled reader coalesces ticks instead of growing an unbounded queue
- /api/file/save clears the same whole-document gate as open-recent:
  409 collab-active during a session instead of silently desyncing the
  runtime's hash and commit log
- document ingest returns a typed outcome; only Committed bumps the
  version, Rejected/Failed surface codes the browser's existing
  conflict recovery already handles, and the local-edit capture closes
  through an RAII guard on every path including unwind
- a ?tenant= request now passes persisted-ACL admission before any
  tenant materialises, so unauthorised probes cannot occupy capacity
- ACL edits and their durable write serialise under one mutex with
  unique temp files; a failed write rolls back and returns 500 rather
  than resurrecting revoked grants after restart
- API-token REST calls are scope-checked by method (session cookies
  exempt) and a token naming no mcp:* scope now gets nothing instead
  of everything — hub-issued tokens must carry explicit scopes
- SIGTERM/SIGINT in the online loop raise the existing shutdown flag
  so container stops run the tenant flush
2026-08-08 17:03:47 +08:00
Kayshen-X f70bf89006 fix(web): unblock session node creation, isolate account switches, persist online tenants
Three review blockers:

- the session push gate refused every id absent from the daemon
  snapshot, which included all ids the namespace allocator mints — so
  nodes created during a session never synced and were erased by the
  next pull. Unknown ids now pass when they parse into this peer's
  namespace (parsed comparison, not prefix match); with no allocator
  the old fail-closed behaviour stands
- a same-tab account switch kept the previous account's document,
  sync baseline, and stored credentials. An identity epoch keyed on
  the auth subject (tri-state, so sign-out then sign-in is not
  mistaken for first contact) resets the sync client, gate, document,
  collab state and latches; 401/403 suppress pushes until the reset;
  browser storage partitions by subject with legacy keys deliberately
  not adopted
- online tenants lived only in memory under the default deploy:
  compose now mounts a data volume, startup fails closed when
  eviction is enabled without a store (OPENPENCIL_ONLINE_EPHEMERAL=1
  is the loud demo bypass), a dedicated sweeper replaces the
  connection-triggered sweep that never ran while idle, and shutdown
  flushes every resident tenant
2026-08-08 16:29:41 +08:00
Kayshen-X 5536018c73 feat(web): cross-account tenant sharing with eviction persistence
Tier-1 online collaboration: an owner grants accounts into their
tenant's ACL (grant/revoke/list under /api/share/), and a visitor
addresses it per request with ?tenant=<ownerId> — query rather than
header because EventSource cannot set headers, and share routes always
administer the caller's own tenant so a grant is not re-shareable.
The wasm shell threads the parameter through its four XHR helpers and
the event stream in one place. A shared tenant has no collaboration
session, so the 409 auto-resolve now also accepts when the daemon
advertises serveMode online (the daemon's counter is the total order
and SSE is already delivering the newer document; the old latch would
freeze a visitor permanently). Evicted tenants persist document + ACL
under sha256(user_id) directories via atomic writes — written before
the registry remove so no instant lacks both copies; unwritable
tenants stay resident and unloadable files are set aside as .corrupt
rather than overwritten. Also restores the Dockerfile entrypoint
literal the M2 CMD restructure dropped (its CI test only ran in the
workspace suite) and splits live_sync_glue under the file cap.
2026-08-08 15:17:57 +08:00
Kayshen-X 79beee711f feat(mcp): capability profile and scope enforcement for the online /mcp surface
A public bearer client previously reached the full 160-tool catalog,
including save_document (fs::write at a caller-chosen path), the
process-global codegen plan store, and host diagnostics. Online mode
now classifies every tool by surface in one reviewable table and denies
the 17 that touch the daemon host (filesystem, outbound fetch,
unscoped globals, diagnostics, future process spawn) in both tools/list
and tools/call — refusal happens before argument parsing, so traversal
payloads never reach a handler. mcp:read/mcp:write scopes gate write
tools ahead of dispatch (deny-then-scope, so a filesystem tool reports
denial rather than inviting a bigger token); session cookies keep full
scope and unclassified static tools fail the build. Local and managed
catalogs are unchanged. The deployment-boundaries script now refuses
direct invocation instead of green-exiting with every helper missing.
2026-08-08 14:34:03 +08:00
Kayshen-X 24c8556f64 feat(web): authenticate online tenants against the op-hub
HubVerifier resolves both credential shapes onto the tenant registry —
Authorization: Bearer through the hub's internal token introspection
(shared-secret header, constant-time compared) and the op_hub_session
cookie through /api/v1/session — so a browser and an MCP token for the
same account share one document. Verdicts cache under SHA-256 keys
(session 60s, token min(300s, expiry), definitive negatives 15s);
upstream failures are never cached so a hub blip cannot become a 15s
outage, and a configured-but-unbuildable hub fails 503 rather than
silently downgrading to the dev verifier. Cookie-authenticated writes
now require an allowlisted Origin (bearer exempt), CORS echoes the
allowlist instead of *, and the web image gains online mode, a non-root
user, and a healthcheck.
2026-08-08 13:59:48 +08:00
Kayshen-X a9149047ec feat(web): multi-tenant online mode for the serve-web daemon
--online turns the single-user daemon into an identity-keyed
multi-account host while leaving local and managed byte-identical
(the legacy serve_one is preserved under cfg(test) to prove it):

- TenantRegistry maps verified user ids to per-tenant WebCanvasState +
  SseHub; leases are taken under the registry lock so a live
  connection can never race eviction, and eviction compare-and-removes
  with the M4 persistence seam marked in place
- ServeMode routes through 11 const predicates, all true for
  local/managed; online locks down file IO, device-login proxy,
  settings persistence, indicators, the POST / alias, generic
  shutdown, sync-reset (safe no-op), LAN/relay collab actions (SSRF
  surface), and the process-global thumbnail registry
- credentials arrive as Bearer or cookie via new HttpRequest capture;
  IdentityVerifier starts with a static env-mapped verifier (hub
  introspection lands in M2); missing verifier fails closed with 503
- /api/ai/* routes moved verbatim to connection_ai_routes.rs to keep
  connection.rs under the file cap
2026-08-08 13:22:19 +08:00
Kayshen-X 5d81c2a480 feat(web): proxy collaboration participant avatars through the daemon
POST /api/collab/avatar resolves a roster participant key to image
bytes via the daemon's public-only HTTPS client, so the browser never
sees signed CDN URLs. The handler lives on the connection thread beside
the account avatar proxy (bounded IO stays off the editor mutex) and
stands in for the desktop's frame-loop avatar pump. VS Code's daemon
contract test now pins the /api/collab auth gate (401 bare, 200 with
the managed token), and the crate map documents the collab-host split.
Browser-side consumption needs an avatarRevision on the participant
wire and an install seam in collab_avatar_runtime; until then web
participants keep the initials fallback.
2026-08-08 12:55:37 +08:00
Kayshen-X 8845c39804 fix(desktop): unblock native CI on windows and de-flake the idle heartbeat test
The locator HSM signer is a unix-socket daemon with no Windows target,
but its unconditional std::os::unix imports failed cargo check on the
windows runner — the socket server and secure-file layers are now
cfg(unix) with a stub main. The transfer-idle heartbeat test pinned its
handshake windows to the same 400ms as the deadline under test, so a
loaded runner could flake the connect phase; the idle deadline (and the
validation-capped handshake windows) now sit at 2s.
2026-08-08 12:48:59 +08:00
Kayshen-X 41860c781b feat(web): mint collaboration-namespaced ids at every browser creation site
Completes the id-namespace handshake on the wasm host: drag-create,
group, boolean-op polyline, duplicate, paste, alt-drag clone, figma
deep-clone import, and layer-context duplicate all route through the
session allocator when one is active, with the standalone counter as
the non-session path. Exhaustion surfaces the ResourceLimit notice and
leaves the document untouched; figma clones stage into a local buffer
so a mid-loop failure cannot land half a paste. The layer-context flow
gains an allocator-aware entry point (split into layer_context_flow.rs
to stay under the file cap; import paths unchanged).
2026-08-08 12:36:56 +08:00
Kayshen-X b3998885d3 refactor(desktop): split chat-tab run binding out of keyboard_input
keyboard_input.rs sat at 802 lines, failing the 800-line cap check in
the collab security boundary gate. Pure code motion of the MT.3
chat-tab launch/drain cluster into keyboard_chat_tabs.rs.
2026-08-08 11:52:34 +08:00
Kayshen-X 0eda62dee4 test(desktop): align collab boundary self-test fixtures with the gate
The mutation self-test has been red since the pairing-code hardening
landed: five new gate anchors (PeerIdentityPolicy, cross-account
admission tests, guest confirmation coverage, VerifiedRelayTokenClaims)
were added without fixture counterparts, and the op-collab-host
extraction moved the runtime paths the fixtures still wrote to. Fixture
files now mirror the scanned layout (including a cfg(test)-pathed
admission_tests module), so all 57 mutation cases pass.
2026-08-08 11:52:34 +08:00
Kayshen-X b8e41fa3c6 feat(web): session-safe sync recovery, SSE fan-in, and collab undo for the browser host
Second collaboration increment on the wasm side:

- a 409 version conflict during an Active session now auto-resolves by
  accepting the sequenced daemon document (rejected local edits stay
  recoverable via the discarded-edit projection); every other phase
  keeps the explicit-resolve latch, with the decision pinned by tests
- the collab tick subscribes to /api/mcp/events via EventSource and
  feeds the same version/collabSeq latch as the poll, with exponential
  backoff down to polling when the stream drops
- Cmd+Z during a session routes through a synchronous RequestUndo wire
  command (claimed => session undo, else local); redo raises the M1
  Unsupported notice
- the session wire carries the peer id namespace and the web host gains
  a collab id allocator (canvas create wired; remaining creation sites
  stay behind the PR4 push gate until converted)
2026-08-08 11:52:10 +08:00
Kayshen-X 2bd181bd60 fix(cli): version guard tolerates and fixtures include the extension manifest
The chrome-extension manifest check crashed jq (exit 2) inside the guard
self-test because the test fixtures never created the manifest, turning
the expected status-1 stale-version verdicts into harness errors. A
missing manifest is now reported as a normal version-sync failure, and
the fixture repo writes a manifest carrying the fixture version.
2026-08-07 22:02:37 +08:00
Kayshen-X ebc681881d feat(web): wire browser collab UI to the daemon /api/collab service
collab_sync drives the already-complete web collab surface against the
PR3 daemon routes, mirroring web_auth_sync:

- adaptive re-arming timer (400ms idle / 150ms in session); collabSeq
  rides the existing version probe and never triggers a document pull
- pending panel actions post as CollabActionWire with single-flight and
  409 collab-busy retry; presence uploads throttle to 100ms
- session-time pushes that would mint collaboration-invalid local node
  ids fail closed until the daemon echoes them (namespace handshake is
  the PR5+ follow-up); mount treats the daemon's sync-reset 409 as
  completion instead of a wasted retry
2026-08-07 21:55:43 +08:00
Kayshen-X 08a26548fe feat(web): daemon-side collaboration service behind /api/collab routes
Give the serve-web daemon a real collaboration runtime so browser and
VSCode-webview editors can drive public-relay sessions through REST,
mirroring the web_auth proxy pattern:

- WebCollabState + a dedicated driver thread (wake channel + 250/100ms
  tick) run CollabRuntime against the daemon document; documentRevision
  and collabSeq are separate so presence/UI changes never trigger a
  whole-document pull
- versioned wire DTOs (CollabStateWire/CollabActionWire, wireVersion 1)
  map through explicit validation onto the internal UI types instead of
  serde on opaque internals; GET state / POST action / POST presence
  routes ride the existing /api/ auth + origin guards
- document pushes during an Active session ingest through a split
  PreparedDocument::prepare (off-lock validation) +
  install_prepared_document (infallible, in-generation) inside a
  begin/finish_local_edit capture; identical pushes produce no txn
- gate_daemon_mutation centralizes session-time refusals (409
  collab-readonly/busy/active) across document push, sync-reset,
  open-recent, /mcp JSON-RPC, and AI apply paths, matching the desktop
  CollabGatePolicy semantics
2026-08-07 21:14:28 +08:00
Kayshen-X 0718e62147 fix(desktop): make collab avatar test-registry guard reentrant
advance_generation takes the cfg(test) avatar registry lock, but tests
that drive it already hold the same non-reentrant mutex, so the runtime
test suite self-deadlocked. Guard entry with a thread-local depth count.
2026-08-07 21:14:07 +08:00
Kayshen-X f779818e9a style(ai): simplify root seed child-emptiness check to satisfy clippy 2026-08-07 19:55:34 +08:00
Kayshen-X 89151683b0 refactor(desktop): extract collab runtime into op-collab-host crate
The 9.8k-line collaboration runtime was welded to the desktop GUI
(WidgetHostNative + winit EventLoopProxy), so the web daemon and future
satellite hosts could not run sessions. Split the host coupling behind a
CollabHost trait + wake notifier closure, then move the runtime, JWKS
fetcher, and tests into the new leaf crate op-collab-host:

- CollabHost (CollaborationEditorHost + dirty/id-namespace hooks) with a
  HeadlessCollabHost for daemon and test use; the WidgetHostNative impl
  lives behind op-host-native's gl-host feature
- async work reaches sync relay/JWKS code through an injected
  BlockingExecutor (process-global OnceLock) instead of depending on
  op-host-services, keeping the crate graph acyclic
- desktop keeps a thin shim (type alias + wake closure); call sequence
  and test assertions unchanged, 154 runtime tests moved as-is
2026-08-07 19:55:26 +08:00
Fini bf93a459b1 fix(agent): inherit artboards for sibling screen continuations 2026-08-07 02:00:23 +08:00
Fini 0b79902570 fix(agent): preserve Kimi K3 design script output 2026-08-07 02:00:23 +08:00
Fini cf49ab88c8 fix(editor): render interactive widgets with authored design tokens 2026-08-07 02:00:23 +08:00
Fini 63198ac4f8 feat(renderer): carry interactive widget semantics through scenes 2026-08-07 02:00:23 +08:00
Fini bc70639f94 fix(ai): require first-class styled interactive widgets 2026-08-07 02:00:23 +08:00
Fini 5743d32ff4 fix(mcp): avoid UTF-8 splits in duplicate script detection 2026-08-07 02:00:23 +08:00
Fini efc8613fa7 feat(ai): import DESIGN.md styles into the asset center 2026-08-07 02:00:23 +08:00
Fini b6688c8818 fix(renderer): measure chrome text in its painted font 2026-08-07 02:00:23 +08:00
Fini 15dad86774 fix(editor): center localized variables empty states 2026-08-07 02:00:22 +08:00
Fini 4561c221b7 fix(desktop): drain stale image decode entries 2026-08-07 02:00:22 +08:00
Fini 348e099d69 refactor(editor): unify agent settings row layout 2026-08-07 02:00:22 +08:00
Fini 08f2dc5d87 feat(editor): add delayed top-bar tooltips 2026-08-07 02:00:22 +08:00
Fini bb01fd54bc feat(editor): refresh deck template visuals 2026-08-07 02:00:22 +08:00
Fini dd97ca1512 feat(editor): refine slide navigator cards 2026-08-07 02:00:22 +08:00
Fini 73732d4edb refactor(renderer): add family-aware chrome text metrics 2026-08-07 02:00:22 +08:00
Fini 599ab11f6c fix(desktop): update casement for direct IME commits 2026-08-07 02:00:22 +08:00
Fini e868a49749 feat(editor): open documents behind a minimized chat bar
Entering the editor or opening a document now docks the AI panel to a
compact 400x48 bar on the canvas's bottom edge — sparkle, the draft or
placeholder, a dimmed model name, and a send disc — and one click expands
it with the input focused. The policy lives in the single document-open
funnel so every entry point inherits it, with one exception: a document
opened while a turn is still streaming keeps the panel up.

The bar is a dock, not a float — it ignores any dragged panel position —
and the whole strip is one target; the model name caps at 45% of the row
so the placeholder always keeps the larger share down to the narrowest
canvas. The old header-only collapsed state retires: is_minimized() maps
persisted collapsed flags onto the bar, and paint locks the bar's weight
(radius, type sizes, one 5%-alpha shadow) so it cannot quietly thicken.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-07 02:00:22 +08:00
Fini 4d7da34e4e feat(editor): rebuild the settings panel as a wide modal with a shared row language
The 720x720 dialog with a 200px side nav becomes a shrink-to-fit modal of
up to 1100x850 with a horizontal icon-tab bar and a full-width content
column. Every tab opens on a hero title with a one-line summary, and one
shared row module supplies the layout language: hairline-separated rows,
right-aligned controls, green iOS-style switches (the one jian Switch call
site gets a success-coloured primary token).

The agents tab lists providers with status pills — connected, not
connected, checking — beside the existing connect and disconnect actions;
the model roster in its hero comes from the shipped presets rather than a
second hand-written list. The MCP tab reshapes into per-CLI toggle rows
with a startup footnote and a custom-config section with a labelled copy
button; the system tab gains a light/dark segmented control writing the
same theme state the top bar toggles. Hit-tests and paints for providers
now share one body-top helper, removing a drifted hand-computed offset.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-07 02:00:22 +08:00
Fini 21917457b0 fix(editor): register the asset center as a text-input owner on both hosts
The gallery's fields were never in input_active(), the one list that says
who owns the keyboard. The platform therefore never opened a composition
session (IME input produced nothing at all), nine letters fell through to
the single-letter tool shortcuts — typing "t" in the search field switched
tools — and the web host, which also lacked an apply_text arm, dropped
every character. The fix is registry-level on both hosts: keyboard
ownership, IME commit/preedit routing ahead of stale canvas text edits,
candidate-window anchoring at the real caret, and copy reading the panel's
own selection. Tests split gate (who owns the keyboard) from routing
(where text lands), because routing-only tests stayed green through the
whole failure.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-07 02:00:22 +08:00