Commit Graph

358 Commits

Author SHA1 Message Date
creatixchu
15a4b512e9 Merge remote-tracking branch 'origin/feat/directory-picker' into feat/workspace-directory-browser 2026-07-29 01:50:30 +08:00
creatixchu
663ac4b50b fix(client): deferRegistration rolls its subscription back when construction throws
The immediate registration attempt runs after the subscription is
installed; a synchronous failure (declared slot already occupied) escaped
the constructor without returning a handle, leaving a subscription owned by
nothing — later ledger flushes would fire it against a failed fiber.
Construction failure now unsubscribes before rethrowing, with a dedicated
deferred.spec covering both arms (ds-review-bot on the flow registrations;
the effect-level rollback in the flow packages keeps covering earlier
successfully-constructed deferrals).
2026-07-29 01:49:58 +08:00
creatixchu
761ceb79f3 fix(host,client): roll back partial flow registrations; cover the widened double
- The browse client half mirrors -native's rollback: a pair construction
  that throws halfway (declared-but-occupied second hole) disposes the
  earlier deferral before rethrowing, so no orphaned slot subscription
  outlives the failed fiber (ds-review-bot).
- The TestWorkspaces browse methods gain the coverage CI's per-file gate
  requires (defaults + stub overrides, recorded like their siblings).
2026-07-29 01:33:16 +08:00
creatixchu
431d7340fa fix(client): teach the IWorkspaces face and its test double the browse calls
master's new ctx.workspaces contract face (IWorkspaces + TestWorkspaces
double) predates this stack's listDirectory/createDirectory; widening the
face is the explicit act of letting the browse flow drive them, and the
double records/stubs them like its siblings.
2026-07-29 01:16:36 +08:00
creatixchu
da71e1eef2 Merge remote-tracking branch 'origin/feat/directory-picker' into feat/workspace-directory-browser 2026-07-29 01:10:33 +08:00
creatixchu
6a78bf4dd0 Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker 2026-07-29 01:09:33 +08:00
creatixchu
51b70f5864 Merge remote-tracking branch 'origin/master' into doc/host-client-group-readmes
# Conflicts:
#	apps/cli/README.i18n.yaml
2026-07-29 01:08:17 +08:00
imccyu
9d9cdd640e style(test-runtime): drop a trailing space 2026-07-29 00:09:40 +08:00
imccyu
3e6a7fea43 test(ui-sidebar): pin the shell chrome with local DOM snapshots
First consumer of the runtime's single-slot mounting: the real apply mounts
on its own fiber, renderSlot('sidebar', ...) captures exactly the sidebar
slot's output, and update() re-renders the collapsed rail in place. The
.snap files carry semantic class names and svg fingerprints only.
2026-07-28 23:54:14 +08:00
imccyu
a3f9a4d3ee test(client): migrate the ui-conversation and web benches onto SlotTestRuntime
The five ui-conversation machinery specs (apply-inject, chat-apply,
chat-toolview-slot, service-orchestration, selection-survival — now .tsx)
and the web app/app-shell specs assemble through the runtime instead of
hand-built Context + SlotsService + fake-session scaffolding per suite.
Session behavior mocks are typed against ISession, so incomplete fakes
fail at compile time.
2026-07-28 23:54:14 +08:00
imccyu
3f7a8c6f8e feat(client): add the dsh-client-test-runtime package
A jsdom slot test runtime for feature specs: a real Cordis Context, the
production SlotsService and web-react renderer, and typed session/workspace
doubles (TestSessions implements ISessions with FixtureSession sessions;
TestWorkspaces implements IWorkspaces), so the compiler flags fixture drift
when a production face changes. Fixtures feed plain data: list rows,
conversation snapshots, and ISession-typed behavior stubs; provide-bundle
materialization runs the shared SessionProvideChannel.

DOM snapshot support: declare()/renderSlot() mount a single slot inside a
data-slot wrapper for local .snap capture, and a snapshot serializer folds
CSS-module class hashes to their semantic locals and collapses svg internals
to a content fingerprint. The typed provide() constrains declared-service
fakes to Partial of the service's outward face.
2026-07-28 23:54:13 +08:00
imccyu
0efc7f045e refactor(client): carve outward interfaces for the session, workspace, layout, slash, and conversation services
Feature packages now reach these domains through interface types only:
ISession/SessionFace (identity + prompt/cancel/loadOlder + the useSession
snapshot source), ISessions, IWorkspaces, ILayout, IConversation, and the
existing SlashServiceContract now actually mounted on Context.slash. The
concrete services implement their face; wire-pump and assembly entry
points stay on the classes. The provide-channel materialization and
current-projection logic moves into SessionProvideChannel so the
production service and the client test runtime share one implementation.
The workspaces service consumes sessions through the narrow SessionsPort.
2026-07-28 23:43:32 +08:00
creatixchu
26ab28083e Merge remote-tracking branch 'origin/feat/directory-picker' into feat/workspace-directory-browser
# Conflicts:
#	packages/host/directory-picker-browse/README.i18n.yaml
2026-07-28 22:31:24 +08:00
creatixchu
95a8e3f949 fix(client,doc): address review — flow-open busy gating, seam on the architecture map, browse gap documented
- While a picking flow is open (native chooser pending, browse dialog up) or
  its pick is being adopted, every other menu action disables: a late outcome
  must not race a concurrent selection or creation (ds-review-bot warning).
- ctx.directoryPicker joins the architecture Capability Services map (both
  languages); neighboring rows condensed to keep the doc inside its ceiling.
- directory-picker-browse documents that its client half lands in the next
  stacked PR: a -browse composition today hides the picking affordance (the
  documented empty-hole default) rather than misbehaving (ds-review-bot
  critical; the dialog itself ships in #821).
2026-07-28 22:29:34 +08:00
imccyu
c46419cf5c fix(apiproxy,client): the list projection column becomes a seedable watermarked block
Review finding (PR #791): the column carried bare values (no seq), so the
client could not seed its value store without risking a stale list block
outranking newer push frames — and nothing consumed the column at all,
leaving cold titles absent after a restart. SessionSummary.projections is
now the same SessionProjectionsBlock as the history tail (values +
asOfSeq; attached rows cut the live registry, cold rows serve the cache's
identity-checked cachedSnapshot whose asOfSeq is the lowest served-row
watermark). SessionManager.refreshList seeds each row's block into the
per-session projection store via per-key apply — partial-baseline
semantics: an absent key never clears, and higher-seq-wins keeps stale
list blocks beneath push frames and tail baselines — so cold titles
surface in the sidebar without opening a session.
2026-07-28 22:26:11 +08:00
creatixchu
4822622cb7 feat(host,client): ship the in-app directory browser as the browse package's client half
directory-picker-browse becomes dual-face: its browser half fills
ui-workspace's two directory-flow holes with the Select Workspace Directory
dialog (figma Harness 813-23126 family — Miller two-column view, breadcrumb
with click-to-edit path zone, nested New-folder dialog), driving the node
half's host.listDirectory/host.createDirectory and owning its locale
namespace (directory-browser, zh default / en). The dialog moves here from
ui-workspace wholesale — the trigger surfaces keep only the flow-hole owner
conversation.

apps/cli flips its one directory-picker row -native -> -browse, swapping the
host backend and the client interaction together; picking now works for
remote deployments out of the box. The keyless workspace-flow snapshot boots
the browse bundle and drives menu -> dialog -> Documents -> project -> Open
against the fixture tree.
2026-07-28 22:22:14 +08:00
creatixchu
05b5059dda Merge remote-tracking branch 'origin/feat/directory-picker' into feat/workspace-directory-browser
# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.md
#	.agents/notes/implemented/architecture/2026-07-28-directory-picker-capability-seam.zh.md
#	apps/cli/cordis.yml
#	apps/cli/package.json
#	docs/module-graph.md
#	packages/client/ui-workspace/README.i18n.yaml
#	packages/client/ui-workspace/README.md
#	packages/client/ui-workspace/README.zh.md
#	packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx
#	packages/client/ui-workspace/src/client/WorkspacePicker.tsx
#	packages/client/ui-workspace/src/client/contract/slots.ts
#	packages/client/ui-workspace/src/client/index.ts
#	packages/client/ui-workspace/tests/apply.spec.ts
#	packages/client/ui-workspace/tests/workspace-browser.spec.tsx
#	packages/client/ui-workspace/tests/workspace-picker.spec.tsx
#	pnpm-lock.yaml
2026-07-28 22:01:26 +08:00
creatixchu
85ca8be104 feat(host,client): compose directory picking through slots — dual-face -native, no wire advertisement
ui-workspace's two trigger surfaces each declare a single-kind directory-flow
hole (conversation.hero.workspace.directoryFlow / sidebar.workspaces.directoryFlow,
same owner contract) and keep only the trigger and the adoption: the Open-local-
folder entry renders while the surface's hole is occupied, and the occupant
reports one picked path per open through the hole's owner conversation
(open/busy/onPicked/onCancel/onError).

directory-picker-native becomes dual-face: its browser half fills both holes
with a renderless occupant driving host.pickDirectory, so the cordis.yml row
that mounts the backend also composes the client interaction — a mismatch is
impossible and a second flow package fails at client load.

With composition wiring both sides, the host.describe.directoryPicker
advertisement and the client's kind branching lose their last consumer:
the field, WorkspacesService.directoryPickerKind(), the DirectoryPickerKind
wire type, and the picker's per-open describe read are deleted. The connection
fixture now serves a deterministic pickDirectory path so the keyless snapshot
drives the full pick-then-adopt flow. ui-workspace's hand-rolled declaration
deferral is replaced by the deferRegistration helper it duplicated.
2026-07-28 21:51:01 +08:00
creatixchu
a94df18bcf Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker
# Conflicts:
#	packages/client/connection/src/client/fixture.ts
#	packages/client/connection/tests/fake-api.ts
#	packages/client/runtime/src/client/workspaces/service.ts
#	packages/client/runtime/tests/fake-api.ts
#	packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx
#	packages/client/ui-workspace/src/client/WorkspacePicker.tsx
#	packages/client/ui-workspace/tests/workspace-picker.spec.tsx
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/host.schema.ts
#	packages/host/apiproxy/src/api/host.ts
#	packages/host/apiproxy/src/api/rpc-map.ts
#	packages/host/apiproxy/src/fetch/client.ts
#	packages/host/apiproxy/src/fetch/handler.ts
#	packages/host/apiproxy/tests/api-proxy-workspace.spec.ts
#	packages/host/apiproxy/tests/client-handler.spec.ts
#	packages/host/apiproxy/tests/fetch-carrier.spec.ts
2026-07-28 21:21:21 +08:00
creatixchu
5579b13503 refactor(host): rename the directory-picker dialog backend and kind to native
The browse interaction also presents a dialog (the in-app modal), so 'dialog'
failed to discriminate the two capability kinds; 'native' names where the
chooser runs. Package directory-picker-dialog -> directory-picker-native, kind
'dialog' -> 'native', with every seam/gateway/client/doc reference updated and
the seam Agent Note's naming rationale rewritten to match.
2026-07-28 21:07:28 +08:00
creatixchu
302dfca4d5 Merge remote-tracking branch 'origin/master' into doc/host-client-group-readmes
# Conflicts:
#	packages/README.i18n.yaml
#	packages/README.md
#	packages/README.zh.md
#	packages/client/connection/src/index.ts
#	packages/client/connection/src/native-dialog-request.ts
#	packages/client/connection/tests/node-half.spec.ts
#	packages/host/apiproxy/README.i18n.yaml
2026-07-28 21:01:20 +08:00
07akioni
a39890bba5 Merge branch 'master' into feat/close-todo 2026-07-28 19:45:36 +08:00
07akioni
2c65ebf6be test(connection): expect fixture todos projection null on empty log 2026-07-28 19:44:36 +08:00
07akioni
7bf36c6aa3 Merge origin/master into feat/close-todo
Keep master's session-projection carrier for todos, and fold turn/start
clearance into the tool-todo projection unit (plus TUI/fixture mirrors).
2026-07-28 19:43:56 +08:00
07akioni
6978dbfb13 fix: cr 2026-07-28 19:16:39 +08:00
Tianyi Cui
291676f704 Merge branch 'master' into xtr/identified-immutable-messages 2026-07-28 18:55:50 +08:00
Chinesezjc
0401c3c6c7 test(ui-theme): resolve elevated surfaces from the palette, not from rebinds
The check added last commit derived its elevated set from the sheets that
already rebind, which cannot catch the omission it targets: such a set only
confirms what someone already remembered, and a surface nobody has rebound
yet defines itself as unelevated. Review found the case that proves it —
TodoPanel scrolls in .list on a --dsw-specific-tip card, the same dark rung
as the menu surface, unrebound and with the derived check green.

Resolves the set from the palette's own dark elevation ladder instead: the
surface tokens whose dark value lands on bg-layer-2 or bg-layer-3, which is
the step the l1/l2 split encodes. A new palette token on an elevated rung is
in scope the moment it is defined.

Scope is by token family rather than geometry: only --dsw-alias-bg-* and
--dsw-specific-* name a surface. The button, interactive, and markdown
families reach the same rungs while naming a control or an inline span that
no scroll container renders a bar against, and shape cannot separate them
since a floating button carries a radius, a shadow, and a fixed size —
ChatView's .toBottom pill was the false positive that showed this.

Adds the missing TodoPanel rebind. Mutation-checked all four rebinds in
turn: each is named with its surface. The palette anchoring has its own
control — narrowing the family pattern turns it red on --dsw-specific-menu.
2026-07-28 18:31:52 +08:00
_Kerman
310e4dff26 test(client): update degraded assistant fixture 2026-07-28 18:24:33 +08:00
Chinesezjc
60248e99d1 Merge remote-tracking branch 'origin/master' into feat/scrollbar-tokens 2026-07-28 18:15:00 +08:00
creatixchu
b211a80b1f fix(host,client): require fully qualified browse paths; clear the picker kind on close
ds-review-bot round 4. On Windows, isAbsolute admits rooted drive-less
forms (\foo, /foo) that resolve() then rebases onto the process's current
drive; both browse primitives now gate on a fullyQualified check (drive
letter or UNC on win32, POSIX-absolute elsewhere) with a platform test
seam, per-platform unit cases, and the contract wording updated on the
seam, the backend README pair, and the error messages.

The picker-kind effect also kept a resolved 'dialog' across close, so a
backend swapped while the menu was closed could paint the stale entry for
one frame on reopen; the close arm now clears the state, pinned by a
reopen-under-pending-read race test.
2026-07-28 18:13:20 +08:00
_Kerman
9220dbd271 Merge remote-tracking branch 'origin/master' into xtr/identified-immutable-messages
# Conflicts:
#	docs/event-producer-consumer.md
#	packages/client/connection/src/client/fixture.ts
#	packages/goal/command-goal/tests/command-goal.spec.ts
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/events.schema.ts
#	packages/host/apiproxy/src/api/events.ts
#	packages/host/apiproxy/tests/api-proxy-view.spec.ts
#	packages/host/apiproxy/tests/rpc-schemas.spec.ts
#	tsconfig.base.json
2026-07-28 18:11:13 +08:00
creatixchu
5f3c53d835 style(web): keep the v8 ignore reasons inline per the invariant rule 2026-07-28 18:04:50 +08:00
creatixchu
99643e59a1 feat(web): miller two-pane directory browser per the full figma flow
The single-column dialog missed the design's interaction model (figma
813-23126/813-23278, sibling frames of the linked node): selection is
not navigation. The browser now opens as one wide level; selecting a
row keeps it marked (pill + open-folder accent) and previews its
children in a second 256px column across a hairline divider, a
right-column pick advances one level, and the breadcrumb follows the
selection. New folder becomes the design's nested create dialog
("New folder in ...", Untitled-folder placeholder, Cancel/Create),
creating inside the selection and landing with the new folder selected.
Open adopts the selection, falling back to the listed level, so the
e2e path-edit flow is unchanged. The card is the design's fixed
600x420 with per-column scrolling.
2026-07-28 18:04:21 +08:00
07akioni
9e94082f4d feat: implement todo plan clearance on turn start 2026-07-28 17:58:09 +08:00
creatixchu
c4bf919895 fix(host,client): default-export the picker seam; invalidate stale kind reads
ds-review-bot round 3. The seam package broke the service-package export
contract (named export only), so the config catalog filed it under Other
libraries and default imports failed; it now default-exports DirectoryPicker
like every abstract seam, and the regenerated catalog lists it as one.

The picker-kind effect also let a settlement from a superseded flow open
leak into the current one (close/reopen mid-describe, or a reconnect that
swaps the backend): the read now resets the affordance on every open and a
cleanup-toggled flag discards obsolete settlements, both directions pinned
by jsdom races.
2026-07-28 17:56:03 +08:00
imccyu
5dd2e77914 Merge remote-tracking branch 'origin/master' into worktree-guifork 2026-07-28 17:48:48 +08:00
Chinesezjc
4265ac876c fix(client): rebind the scrollbar indirection on three missed elevated surfaces
Review found three scroll containers sitting on surfaces the rebinding
contract covers, none of which rebound: ui-primitives' shared Menu card
on --dsw-specific-menu (the surface PopupSelectView already rebinds for),
and the composer input and question composer cards, both on
--dsw-specific-input-major. Each rendered the l1 thumb, which differs
from l2 only in the dark palette and only on that surface, so a
light-palette screenshot and a code read both look correct.

Adds the mechanical check that would have caught them instead of leaving
it to inspection: a sheet that scrolls somewhere and paints a known
elevated surface somewhere must rebind. The elevated set is derived from
the sheets that already rebind, since a rebinding rule paints the surface
whose elevation it declares, so a new elevated surface joins the set by
rebinding rather than by anyone updating a list. Surface-level rather than
element-level because the card and the descendant that scrolls are
separate rules and CSS text does not say which contains which. Verified by
reverting each of the three fixes in turn: the check names the sheet and
the surface every time.

Also commits snapshots/sidebar-scrollbar/geometry.expected.md, the
resolved scrollbar style and geometry in both palettes. The aria goldens
the other web scenarios commit cannot carry a CSS-only change, since it
alters no DOM and no accessible name and leaves their trees
byte-identical. Absolute coordinates stay out: they track font metrics and
the laid-out sidebar width, so committing them would document the platform
and force a per-platform re-record.
2026-07-28 17:42:57 +08:00
creatixchu
cd7aa3c7d8 fix(host,client): gate the picker affordance on the advertised kind; reject non-absolute browse paths
ds-review-bot round 2. The workspace UI never consulted the advertised
directoryPicker kind: under a browse (or merge-added) backend it still
rendered 'Open local folder…' and called pickDirectory(), which the host
answers with directory-picker-unavailable. The create flow now reads
directoryPickerKind() per menu open and renders the dialog affordance only
under 'dialog' — browse (until its in-app browser UI lands) and unknown
kinds hide the entry, realizing the seam's documented default; a keyless
workspace-flow snapshot pins the hidden entry over the browse fixture.

The browse backend also resolved wire paths, silently rebasing '' or
relative parents under the host process cwd; both primitives now reject
non-absolute explicit paths with their business codes, and the seam JSDoc
carries the contract.
2026-07-28 17:39:15 +08:00
creatixchu
2b64341bc0 fix(web): align the directory browser with the figma frame and merge the seam tip
The dialog now owns the figma structure through a new headless Modal
mode (mask/card/Escape stay shared): header block with the title and
crumbs 8px apart above the l3 separator (no close chrome — the figma
frame has none), 16px to the level, and the 12px card bottom. The
picker-kind narrowing returns for the merged open describe kind — an
unrecognized advertised kind hides the local-folder entry, now covered
alongside the stale-navigation failure arm and the unmount races.
2026-07-28 17:33:19 +08:00
creatixchu
ee5deee0de Merge remote-tracking branch 'origin/feat/directory-picker' into feat/workspace-directory-browser 2026-07-28 17:24:50 +08:00
creatixchu
006a6655ee feat(web): in-app workspace-directory browser as the shipped picking default
The Open-local-folder flow now branches on the Host's advertised picker
interaction (host.describe.directoryPicker, read per menu open; unknown
kinds hide the entry): dialog keeps the native-chooser flow, and browse
opens the new in-app directory browser (figma Harness 802-56979) —
breadcrumbs rooted at a localized Home crumb, a click-to-edit path zone
right of the crumbs, host-flagged hidden entries filtered client-side,
an inline New-folder row, and Open adopting the listed directory
through the existing workspace-creation error surface. Dialog copy is
localized (ctx.locale, namespace 'workspace'); the plugin re-registers
its entries on locale/change.

apps/cli flips the composed backend from -dialog to -browse, so the
picker works for remote deployments out of the box; -dialog stays a
composable alternative. The workspace-management e2e drops its native
picker monkey-patch and drives the real modal end-to-end via the
path-edit affordance.
2026-07-28 17:15:39 +08:00
creatixchu
6e299cd55a Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker 2026-07-28 17:10:00 +08:00
creatixchu
c565022c8a fix(host): derive the picker capability union from a merge-extensible map
ds-review-bot round 1: the seam documented a merge-extensible union but
shipped a closed alias, and the gateway schema rejected any kind beyond
dialog/browse — a third backend could neither implement the seam nor be
advertised. The union now derives from an augmentable DirectoryPickerCapabilities
map, host.describe.directoryPicker preserves unknown wire kinds, and the
browse fixture applies listDirectory's root special case so creating under
'/' no longer mints a '//name' identity.
2026-07-28 17:09:43 +08:00
creatixchu
772653464d fix(connection): hold markerless requests to the Host fence — plain-HTTP browser reads carry no markers
Fetch-Metadata and Origin are only attached to trustworthy destinations, so
over plain HTTP a rebound page's same-origin GET (EventSource, images,
navigations) arrives with no browser markers and a readable response. Remove
the marker shortcut; non-browser clients pass the same fence via loopback,
the CLI-derived LAN IP literals, or a declared authority.
2026-07-28 17:02:39 +08:00
imccyu
de56936c87 docs: fix test and docs conflicts 2026-07-28 16:50:07 +08:00
creatixchu
7ff8da56df fix(connection): require trustedHosts entries in canonical authority form
A dangling colon (harness.internal:) or zero-padded port parses cleanly while
WHATWG silently rewrites it, turning an intended exact-port grant into an
any-port grant. Replace the character blacklist with a round-trip check: an
entry must read back from parsing exactly as written (case aside), refusing
the whole rewrite class at load.
2026-07-28 16:49:52 +08:00
creatixchu
0fcfc5214b Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker 2026-07-28 16:42:36 +08:00
Chinesezjc
75ae2cb4eb Merge remote-tracking branch 'origin/master' into feat/scrollbar-tokens 2026-07-28 16:23:44 +08:00
creatixchu
34518cb012 fix(connection): judge an entry's explicit port from the parsed URL, not the raw string
WHATWG trimming strips stray whitespace before parsing, so 'host:port ' passed
the load assert while the raw-string port regex read it as port-less —
broadening an exact-port grant to every port on that hostname. The explicit-
port judgment now reads URL parses under both special schemes (:80/:443 stay
explicit), and the load assert refuses whitespace outright.
2026-07-28 16:19:31 +08:00
imccyu
9ce201e490 Merge remote-tracking branch 'origin/master' into worktree-guifork 2026-07-28 16:12:12 +08:00