Conversation
There was a problem hiding this comment.
Pull request overview
Adds a v3 planning document describing how the project will reintroduce v1’s Smart-HTTP/OCI/federation capabilities within v2’s kernel/extension architecture, along with an increment-by-increment roadmap and verification criteria.
Changes:
- Add
V3_PLAN.mddescribing v3 goals, architecture targets, increment plan, and verification checklist.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Forgepoint v3 — Plan | ||
|
|
||
| This branch combines what v1 (the original Forgepoint at `forgepoint-dev/forgepoint`) and v2 (the spec-driven rewrite, which this repo is) each got right into a single coherent kernel. |
v3 architectural foundation — landedAll 8 increments planned for this initial branch are committed and pushed. The v3 architecture combining the best of v1 (pure-Rust Git Smart HTTP v2, OCI extension distribution) with v2 (kernel-vs-features split, OIDC + SpiceDB + CUE + CloudEvents + WIT/Component Model, spec discipline) is in place at the contract and crate level. What's done
Test posture
Backend flip
Deferred to follow-up branchesSee
Each is independently scoped and individually shippable on its own branch. 🤖 Generated with Claude Code |
…ames Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…group-path identity
…in.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add split_repo_path helper and build_repository_summary to derive groups from the path field, count open pull requests, compute check pass/total summary, and carry lastCommitAt. Wire workspace.repositories into the GraphQL response as enriched summaries alongside repositoryByPath. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gthen tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d:true v1 flag Adds three host-side aggregated viewer fields consumed by the home your-work widget; the aggregated:true flag marks them for replacement by the federated GraphQL planner (V3_PLAN item 9). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `filter_events_for_viewer` helper that keeps only activity events whose `repositoryID` is in the caller-supplied visible set (events with no `repositoryID` are dropped defensively). Wire it into the workspace GraphQL projection as `workspace.events`; v1 visible set is all repos in the workspace. Leave `activityEvents` top-level field untouched. Two unit tests cover include/exclude logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SDK setup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion guards Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…K setup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces the ext_workspace_home first-party extension with v2 UI manifest contributing home.your-work, home.repositories, home.activity, and home.instance slots via four custom elements. Also registers the extension in FIRST_PARTY_EXTENSIONS so the server loader picks it up on startup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Appends smoke assertions that verify the new Astro routes introduced in Tasks 12-14 produce the expected SSR HTML and status codes: home-shell on /, repo-dashboard on /r/<path>, extension-page on /x/pulls/, 404 for unknown repo and extension prefixes, and the /instance -> /#instance redirect. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Captures all in-flight v3 scaffolding from prior sessions before the M0 audit begins. Everything in this commit is contract / scaffolding — none of it is wired into the live request path, by design. The cutover that wires it (and deletes legacy code) starts here. Includes: - Platform WIT contract @ comtrya:platform@0.1.0 (extensions/wit/...) - Host-side WIT bindings module (crates/server/src/wasm_host.rs) - WIT codegen crate (crates/wit-codegen/) - Per-extension WITs + updated manifests for the five first-party extensions - Bundler script (extensions/bundler/build-extension.sh) - Framework-agnostic + Vue + Preact SDK packages (frontend/packages/) - Design + phase log + extensions docs (docs/) - GOAL.md describing the cutover plan and working process - M0 deliverable: docs/v3-deletion-inventory.md The deletion inventory drives every subsequent milestone.
Adversarial review caught real accuracy bugs in the first draft. Fixed: - Arm count corrected from 28 to 29 - `comments_delete_mutation` end line corrected to 3079 (was 3093) - All five storage-seed line numbers corrected (off by 25-157 each) Added rows for artifacts the first draft missed: - `fn graphql_response` at 3350 — the monolithic snapshot path the Astro frontend depends on; dies in M9 - `fn extension_resolver_payload` at 1657 — legacy resolver dispatch for ext_checks/ext_workspace_home/ext_code_browser; phased out through M5/M11 - `fn checks_resolver_output` (4873), `pull_request_resolver_output` (4842), `code_browser_resolver_output` (4821) - `struct CheckSummary` (4959) + `fn check_summary` (4966) - Frontend test files list under M10 - `frontend/src/pages/[...path].ts` API-proxy callout Added an `ext_code_browser` status section — it's now core, not an extension; the in-process resolver branch is retained with an open question about whether to fold it into a dedicated GraphQL query before M11 deletes its host (`extension_resolver_payload`). Flips M0 boxes 1-7 in GOAL.md. WIT lock and pre-flight decisions follow in their own commits.
Adds a clear LOCKED banner to package.wit specifying the revision-bump procedure. wasm-tools still parses cleanly. No drive-by edits to the WIT during the cutover. Flips the M0 lock box in GOAL.md.
Locks the five GOAL.md pre-flight questions into docs/v3-decisions.md: SPA (not SSR), Vite (not Astro), seed payload + WASM-driven seeder, keep GraphQL schema, pin wasmtime 43.0.2 + wit-parser 0.235 (cargo- component pinned at M1 first use). Flips the final M0 box. M0 is complete.
First real Component-Model WASM in the project. The component
implements every op declared in extensions/first-party/ext_issues/wit/
issues.wit (open-issue, close-issue, reopen-issue, get-issue,
list-issues) backed entirely by platform host imports:
- ids.mint("issue") for opaque IDs
- storage.{create,get,update-begin,update-commit,list-all} for state
- events.append for dev.comtrya.issues.{opened,closed,reopened}
- time.now-iso for timestamps
- identity.current-principal for the actor
The reactor export returns empty in 0.1.0 — ext_issues subscribes to
nothing. ext_pull_requests will subscribe in M6.
Build chain:
- cargo-component 0.21.1 + wit-bindgen-rt 0.44 pinned
- Component built for wasm32-unknown-unknown (NOT wasm32-wasip1) so no
WASI adapter imports leak into the component's import set. The
.cargo/config.toml in the component crate locks this target.
- Workspace exclude added for extensions/first-party/*/component so
the host workspace doesn't try to compile WASM crates.
- Bundler script (extensions/bundler/build-extension.sh) updated:
builds the component under <ext_root>/component/, copies wasm to
<ext_root>/dist/, and runs wit-codegen with the platform WIT as a
dep so the per-extension WIT's `use comtrya:platform/...` resolves.
- wit-codegen now skips exports whose interface lives in a different
package from the per-extension WIT — so reactor exports (from
comtrya:platform) no longer pollute the dispatch table.
Verified end-to-end:
- `bash extensions/bundler/build-extension.sh extensions/first-party/ext_issues`
emits dist/ext_issues.wasm (108KB), ext_issues.handlers.rs (5 ops),
ext_issues.client.ts.
- `wasm-tools component wit dist/ext_issues.wasm` reports the
component imports only comtrya:platform/* interfaces and exports
comtrya:ext-issues/issues@0.1.0 + comtrya:platform/reactor@0.1.0.
Flips M1 boxes 1-9 in GOAL.md. Box 10 (Wasmtime integration test
calling close-issue) lands in its own commit.
Toolchain pins recorded in docs/v3-decisions.md.
Box 10: wasm_host::m1_ext_issues_smoke::close_issue_round_trip loads ext_issues.wasm, builds a HostState with a tempdir-backed ExtensionRuntimeStore, instantiates the component through make_platform_linker, calls open-issue then close-issue via a second wasmtime::component::bindgen! pointed at the ext-issues WIT, and asserts the persisted issue record's state == "closed". Proves the full host-import contract round-trips: ids.mint, storage.create, storage.update-begin/commit, events.append, time.now-iso, identity.current-principal all behave under real WASM dispatch. Milestone-boundary adversarial review fixes: - I1 (next_issue_number O(N) + 1024 collision cap): replaced list_all scan with OCC compare-and-swap on a per-repository counter in a _meta collection. No more silent duplicate numbers. - I2 (UlidMinter kernel-internal kinds): added UlidMinter::with_kernel_kinds() that pre-seeds event, relation, comment so neither the test nor M2 production wiring has to remember the kernel-internal kind list. - I7 (test panics if WASM missing): now skips with an actionable message so `cargo test` on a fresh clone still works. Review findings tracked as named follow-ons in GOAL.md: - M2: mint_internal helper for kernel-initiated mints (I3) - M2: bundler guard for missing .cargo/config.toml (I5) - M2: enforce ids.mint -> storage.create registry contract - M2: extension load path must read the real wasm artifact, not manifest.wasmComponent's component.wat stub (C2 deferred to M2, not a M1 blocker — current resolver path still reads .wat) - M4: persist close-issue input.reason (I4) Reviewer claim C1 (wit-bindgen-rt 0.53 required) was incorrect — verified via `cargo search wit-bindgen-rt`: 0.44 IS the latest published version. wasmtime 43.0.2 transitively brings wit-bindgen 0.51 and 0.57; the canonical Component-Model ABI is stable across this gap for the M1 surface (plain records, scalars, lists, options, results, variants). Empirically confirmed by the passing smoke test. Recorded in docs/v3-decisions.md. M1 complete: every box ticked, every CRITICAL/IMPORTANT finding either resolved or tracked at a named milestone.
build.rs walks extensions/first-party/ and, for any extension that
declares platformWitVersion AND ships a component/ cargo-component
crate, runs comtrya-wit-codegen against its wit/ and emits
<OUT_DIR>/<ext_id>.handlers.rs. Composes a top-level
<OUT_DIR>/dispatch_table.rs that defers to each per-extension
dispatch_route_<ext_id> function and re-exports DispatchInfo.
The build script:
- Issues cargo:rerun-if-changed for every WIT file + manifest, so
edits trigger a rebuild.
- Skips extensions whose manifest declares platformWitVersion but
don't yet have a component/ crate (cargo:warning surfaces the
skip in build logs). Today that's ext_epics + ext_pull_requests
pending M5 migration.
- Auto-creates the wit/deps/platform symlink so a fresh clone +
cargo build works without first running the bundler.
main.rs gains:
- mod generated_dispatch { include!(... dispatch_table.rs ...); }
- Two tests verifying the codegen → include pipeline works
end-to-end: ext_issues.issues.close-issue resolves to a
DispatchInfo with the right fields; an unknown route returns None.
Flips M2 boxes 1-2.
Extensions whose manifest declares platformWitVersion AND ship a
real <ext_root>/dist/<ext_id>.wasm now take a new load path:
- The kernel builds a single WasmRegistry on boot, holding one
Arc<Linker<HostState>> + Arc<Engine> + the loaded-extension map.
- Each platform extension's manifest is parsed into HostManifest
(allowedEmits, allowedEventReads, allowedCrossCalls, reactor
allowlists, contributes.resourceKinds, hostImports).
- The WASM binary is read from dist/<id>.wasm (not the legacy
manifest.wasmComponent which still points at component.wat for the
legacy resolver path).
- The registry pre-seeds kernel-internal kinds in the UlidMinter via
with_kernel_kinds; each registered extension adds its declared
kinds via the new IdMinter::register_kind trait method.
- Extensions without platformWitVersion keep the legacy resolver
path (Linker::<()> + resolve()) until their migration milestone.
RegistryDispatcher implements OpsDispatcher backed by the registry.
For M2, cross-extension calls return error-code::Unavailable with a
clear message pointing at M5+ when per-extension typed bindings land
— the registry lookup, manifest enforcement, and depth tracking all
work; only the WASM-side typed call is stubbed.
Side fixes surfaced while wiring:
- Pre-existing bug in is_valid_permission_grammar accepted ".write"
as valid (leading dot). Rewrote using split('.') with non-empty
halves; all original cases still pass plus the leading-dot case
now correctly rejects.
- IdMinter gained a register_kind(&str, &str) trait method so the
registry doesn't need to downcast to UlidMinter at load time.
Tests:
- registry_loads_ext_issues_from_manifest verifies a real
ext_issues.wasm + manifest produces a LoadedExtension with the
right principal, contributes_resource_kinds, allowed_emits.
- registry_get_unknown_returns_none verifies the not-found path.
- parse_host_manifest_handles_missing_optional_fields verifies the
manifest parser doesn't panic on a minimal manifest.
- m1_ext_issues_smoke and dispatch_table tests still pass.
Flips M2 boxes 3, 4 (added), 5, 7. Box 6 (live request path) stays
for M3 — that's GraphQL handler wiring.
- Add HostState::mint_internal for kernel-initiated mints (events.append, relations.create, comments.post). The Host trait impls now use this helper, so the split between extension-initiated mints (manifest- gated, return Forbidden on missing kind) and kernel-initiated ones (no gate, fail Internal if the kind isn't registered with the minter) is explicit at every call site rather than implicit. - Bundler script (build-extension.sh) now FAILS if a component crate lacks .cargo/config.toml. Previously cargo-component would silently fall back to wasm32-wasip1 and drag every wasi:* import into the component, making it un-instantiable under our Linker<HostState>.
The repo overview rail already surfaces projects, bookmarks, labels
and the workspace-wide activity stream, but nothing on the page
answered "what just landed in this repo?". The kernel already pre-
computes the last 8 commits via `git_commits` and exposes them as
`repository.commits { oid shortOid subject author time }`; this
slice just renders them.
A "Recent commits" panel sits between the bookmarks panel and the
labels catalog in the overview rail. Each row shows the short oid
(full oid as the cell tooltip), the subject, the author, and git's
own `--date=relative` string — same shape GitHub's repo-home uses,
but our copy stops at 8 instead of paginating so the rail stays
scannable.
The panel hides itself when there are no commits, so freshly-
imported empty repos don't earn a placeholder. Once the jj
change-id workflow lands (see memory), this is the row that grows
a third column for the change-id.
Verified live on /r/comtrya/comtrya (2 commits) and
/r/comtrya/dogfood (1 commit): heading "Recent commits", rows
render with `<oid> <subject> <author> <time-ago>` and link
through to the underlying full oid via tooltip.
This commit was created with the assistance of a LLM.
…aceholders
Iter 47-48 added issue / pull / epic title search to the palette;
this slice rounds out the entity surface by surfacing every
workspace repository as a fourth group ("Repos"). Selecting a repo
routes to `/r/<path>`, the same destination the registered
`core.switch-to-repository.*` commands already produce — but the
new rows carry the repo description, so the palette gives more
context per row than the bare command titles do.
Empty-query reveals the first 8 repos, which makes the palette
double as a quick repo switcher (the way Linear's `cmd+K` reveals
recents on open) rather than only filtering when the user starts
typing.
Both placeholder strings get the truth update: the topbar `cmdk`
button no longer claims to search "file, ref" (which the palette
has never done), and the palette input's "Search commands"
becomes "Search repos, issues, pulls, epics, commands…".
Verified live: cmd-K with no query shows a "Repos" group with all
5 workspace repos; typing `dogfood` narrows to one row; clicking
the row navigates to `/r/comtrya/dogfood`. The registered-command
"Repositories" group stays alongside (visually distinct header)
until a later refactor.
This commit was created with the assistance of a LLM.
The comment-thread element was read-and-post only; once a comment was posted there was no way to fix a typo or drop a stray remark. The kernel already exposes `comments.update` and `comments.delete` through the GraphQL dispatcher; this slice surfaces them in the row. Each comment now grows an actions strip (revealed on row hover / focus) with `edit` and `delete` buttons. Edit swaps the rendered body for a textarea + Save / Cancel chrome; Save calls `comments.update`, replaces the body with the new markdown render, and stamps an `edited` marker into the meta row. Delete confirms through `window.confirm` and calls `comments.delete`, dropping the row optimistically. Both modes accept Cmd / Ctrl-Enter as the save chord, matching the composer's iter 52 keyboard. Verified live on /x/issues/.../2: clicking edit, replacing the body, and clicking save reflows the row with the new markdown and the `edited` marker; clicking delete drops the row from 4 → 3. This commit was created with the assistance of a LLM.
IssuesList was returning issues in whatever order `list-issues` emitted (effectively number-desc), so a closed issue from last week sat next to a freshly-opened one with no visual cue about which had recent activity. Bring it in line with PullsQueue (which already sorts by `updatedAt ?? createdAt` desc) so every queue surface reads the same way. Falls back through `updatedAt → createdAt → number` so a batch of issues sharing a second-resolution timestamp still surfaces the newest number first instead of looking randomly ordered. The `updatedAt` field was wired through in iter 43, so the data is already on every row. Verified live on /x/issues/?state=all: closing issue #1 via the ops endpoint floats it to the top of the list ("just now"); the remaining issues, all sharing "1h ago", fall back to number-desc (#6, #5, #4, #3, #2). This commit was created with the assistance of a LLM.
The thread's status line was a one-shot — "Loading…" → either "No comments yet" or removed entirely once rows landed. Promote it to a persistent count header so a reader can see "3 comments" at a glance without scanning the list. Updates live on post, delete, and the initial fetch. The element also now dispatches `comment-thread-update` events (detail.count, bubbles) on every count change. Host pages can listen and render their own counted heading — e.g. "Activity (3)" in IssueDetail's section header — without scraping the DOM. Verified live on issue #2: status reads "3 comments" → posting via cmd+Enter flips it to "4 comments" → delete drops it back to "3 comments"; the event payload tracks (4, 3) on the listener. This commit was created with the assistance of a LLM.
…faces Iter 58 made the comment-thread element dispatch a `comment-thread-update` CustomEvent on every row change; this slice wires the three extension detail surfaces (IssueDetail, PullsDetail, EpicDetail) to that event so each header reads "Activity (3)" / "Discussion (3)" without scraping the custom element's DOM. EpicDetail didn't have a header above its comment thread at all — the section just hung off the page. Added a `<header>` with "Discussion" so it matches the other two surfaces. Verified live: /x/issues/.../2 → "Activity (3)" /r/comtrya/comtrya/pulls/pul_…/42 → "Discussion (0)" /x/epics/<ws>/epc_… → "Discussion (0)" This commit was created with the assistance of a LLM.
Inbox rows show number / title / labels / project / repo / state, but nothing about *when* the work last moved. A triager pulling up the inbox to decide what to tackle had to click through each row to see the age. Add a small "X ago" chip on the right of the meta row for both Open pull requests and Open issues panels, mirroring the chip IssuesList and PullsQueue already use. Full timestamp is in the chip's `title` for ISO-precision hover. Inputs are already sorted by `updatedAt` desc (iter 32), so the chip sequence reads top-down as "most recent first" — which is what the sort silently promised but didn't visualise. Verified live on /inbox: both panels render "2h ago" age chips on their rows. This commit was created with the assistance of a LLM.
Cmd-K results were a wall of titles with no visual indication of why each row matched the query. Borrow the Spotlight / VSCode / Linear convention: wrap the matched substring inside each result title in a `<mark>` styled as a bold span with an accent-orange underline. Highlight applies to issues, pulls, epics, and repos. The repo row shows it on the path (`comtrya/<mark>dogfood</mark>`); the entity rows show it on the title (`Q4 <mark>platform</mark> launch`). The helper HTML-escapes its input before wrapping the match, so user- provided titles can't smuggle markup through the v-html sink. Verified live: typing `platform` underlines the matched substring in the "Q4 platform launch" epic; typing `dogfood` underlines the substring in the `comtrya/dogfood` repo path. This commit was created with the assistance of a LLM.
Iter 55 added a "Repos" entity-search group to the palette, with each row carrying the repo description — strictly more informative than the "Switch to repository <path>" commands `bindRepositoryCommands` registered. The duplication showed up as two groups with overlapping items, so iter 55 routed around by calling the new group "Repos" while the old one kept the "Repositories" header. Now that the entity group is canonical, drop `repository-commands.ts` entirely and rename the entity group back to "Repositories". The palette emits a single source of repo nav; the bundle loses one SSE subscription and a per-repo command registration loop. Verified live on /: cmd-K with no query shows one "Repositories" group; no "Switch to repository …" rows appear elsewhere. This commit was created with the assistance of a LLM.
First slice of the change-id workflow (see memory
project_jj_change_id_workflow.md). Plumbing + UI consumer in the
same iteration per loop bias:
* kernel: `git_commits` extends the `git log` format to capture
the `Change-Id:` trailer via git's built-in
`%(trailers:key=Change-Id,valueonly)`. `-z` separates commits
with NUL because the trailer format unconditionally prints a
trailing newline. `repo_git_data` previously had its own inline
duplicate of the commit-parsing loop — refactor it to call
`git_commits` so the change-id (and future commit-shape
additions) live in one place. The fn also now reads the
default branch instead of hardcoding `main`.
* frontend: RepoHome's RepositoryCommit interface gains
`changeId?: string | null`; the GraphQL query asks for it; the
Recent commits row renders an accent-teal short-id chip next
to the short oid when a trailer is present, with the full
change-id in the cell tooltip. Commits without a trailer
simply omit the chip — git-only repos stay clean.
To make the rendering visible in the running stack, the demo
repo's "Wire live Git and extension demo data" seed commit now
carries a `Change-Id:` trailer in its body (one of two demo
commits — the seed commit stays trailer-free so both shapes are
exercised).
Verified live on /r/comtrya/comtrya: top commit renders
`c35bc75 [I9d2c3f7] Wire live Git ...` with the full change-id in
the chip's tooltip; second commit renders without the chip.
This commit was created with the assistance of a LLM.
Iter 39 wired `c` to navigate to `/x/issues/new` on any surface without an inline quick-add. On detail pages — IssueDetail, PullsDetail, EpicDetail — that left a reviewer who pressed `c` mid-thought yanked away from the page they were reading. Composing a reply is the natural "create" verb on a detail surface. The create chord now checks for a visible `[data-smoke="comment-thread-composer"]` first; if its textarea is findable, focus it (and scrollIntoView so the composer lands in the viewport on tall pages) instead of navigating. Falls through to the iter 41 routing logic everywhere else, so Inbox still routes to `/x/issues/new`. Shortcut overlay copy updates to match: "Comment on this surface — or create new (Inbox, detail pages)". Verified live: `c` on /x/issues/.../2 focuses the composer textarea without changing the URL; `c` on /inbox still navigates to /x/issues/new. This commit was created with the assistance of a LLM.
ActivityStream's `dev.comtrya.comment.posted` case already reads
`stringField(payload, "body")` and slices it to ~80 chars for the
stream row text — but the kernel was emitting the event with only
`{commentID, target, parent, authorRef}`. The reader fell through to
"comment", leaving the stream row opaque ("commented · comment").
Add a 200-char body preview to the event payload (the activity row
re-truncates to 80, so the cap is just a guardrail against multi-
kilobyte comments inflating event-log size). No client change
needed — the ActivityStream code already projects the field.
Not visually verified in this dev env: workspace.events resolves
to a hardcoded demo array in v1, and the SSE channel requires an
access token the dev browser doesn't carry. The change is a small,
correct enabler for the existing reader; production deployments
gain the preview without any further wiring.
This commit was created with the assistance of a LLM.
A small localStorage-tracked list of the user's last 5 visited routes, surfaced as a new "Recent" section in the sidebar above the Repositories list. Each entry shows a friendly label (`issue #4`, `pull pul_…`, `epic <short>`, `<repo>/<tab>`, or the repo path) and links to the route. The list is browser-local — no kernel state, no sync, no per-account history surface yet. `recordRouteVisit(path, label)` fires from `router.afterEach`. The shouldRecord guard filters out routes the sidebar already exposes via direct links (`/`, `/inbox`, `/new`) and any `*/new` form so the section reads as recall-worthy destinations only. Verified live: clearing localStorage, then navigating issue #2 → /r/comtrya/comtrya → /r/comtrya/dogfood/issues produces a "Recent" section with three rows ordered most-recent first. This commit was created with the assistance of a LLM.
Cmd-K on a fresh palette open now shows the iter 66 recent-routes list as the first group, above Navigation / Create / Projects / Repositories. Each row carries the friendly label (`issue #4`, `<repo>/<tab>`, etc.) plus the raw path in the meta-code slot for disambiguation, and selecting it routes there. The group hides itself the moment the user starts typing — at that point the intent is search, not recall, and recents become noise. Hooks into the same `recentRoutes` ref the sidebar reads from, so the two surfaces stay in lockstep without an extra subscription. Verified live (after the iter 66 navigation seeded three recents): empty palette shows "Recent" with 3 rows leading the list; typing `platform` collapses to just the Epics match. This commit was created with the assistance of a LLM.
A small `×` icon sits next to the "Recent" header, calling `clearRecents()` — the new export on `recents.ts`. Drops the reactive ref to an empty list and writes `[]` to localStorage so the reset survives reloads. Useful before screen-sharing or to purge a stale jump-list. The section is `v-if="recents.length > 0"`, so it disappears cleanly the moment the list empties — no awkward "Recent (empty)" state. Reuses the existing `.sb-icon-link` chrome so the button matches the `+` next to "Repositories". Verified live: starting from 3 recent rows, clicking the clear button drops the panel from the DOM and writes `[]` to `localStorage["comtrya.recentRoutes"]`. This commit was created with the assistance of a LLM.
Each comment row gains a "reply" action alongside edit / delete.
Clicking mounts an inline composer (same shape as the edit-mode
textarea, with Cmd-Enter to post) and posts a child comment with
`parent` set to the parent's URI. Replies render nested inside a
`comment-thread-replies` UL indented under their parent, with a
thin rule on the left edge so the hierarchy reads.
Render loop groups comments by parent before laying them out:
each top-level comment is rendered, and its replies recurse into
the nested UL. Replies whose parent isn't on the page (kernel
permits orphans after a parent is deleted) surface at the top
level so they stay visible.
Two kernel-contract details baked in:
* `parent` on the wire is a `comtrya://comment/<id>` URI — the
component wraps the bare id at post time and strips the
prefix when grouping so the in-memory tree keys cleanly.
* The `:scope > li.comment-row` count selector (iter 58) is
unaffected because replies live in a separate UL, so the
"N comments" header now counts top-level only.
Verified live on /x/issues/.../2: pressing "reply" on an existing
comment opens an inline composer; submitting via Cmd-Enter
appends the reply as a child row, the composer closes, and the
reply persists nested under its parent on reload.
This commit was created with the assistance of a LLM.
A freshly-posted comment shows "just now" and stayed that way until the page reloaded. Add a 60-second tick that walks every `<time datetime=...>` inside the thread and recomputes the text via the same `relativeTime` helper used at render. After a minute, "just now" rolls forward to "1m ago"; an hour later, "1h ago". The interval is set up in `connectedCallback` and torn down in `disconnectedCallback` so navigation away from the page doesn't leak timers. Cheap walk — a few dozen `time` elements per thread at most. This commit was created with the assistance of a LLM.
OKLCH color palette, glass surfaces with backdrop-filter blur, Geist/Instrument Serif type stack, 0.5px borders, and dark-first color-scheme. Renames all design tokens across the shell and every first-party extension UI bundle.
Adds the utility primitives from the Claude Design handoff bundle: glass/glass-thin, hairlines, chips (chip-ok/warn/err/info/accent + chip-mono/dot), buttons (btn/btn-ghost/btn-primary + btn-sm/btn-lg), avatar, tabs, progress bar, sparkline strokes, window chrome dots, cmt-logo/cmt-glyph, code-row + diff + syntax tokens, eyebrow, kbd, mark, plus --add/--del color tokens. Additive only — coexists with the existing .chip.ok style API; no existing selector is modified.
Below 640px: - Topbar drops the search-text label, keeps the kbd hint - Sidebar repository and recent lists scroll horizontally with snap points - Footer nav (Instance/Health/Settings) wraps as a flex row - Buttons grow to 32px height for thumb-friendly hit areas Below 720px: - Workspace summary grid drops to 2-up Below 860px (existing breakpoint, refined): - Workspace summary grid stays at 2-up instead of collapsing to 1
# Conflicts: # frontend/src/styles.css
feat(frontend): dark glass design system + utility primitives + mobile responsive
Five new routes ported pixel-faithfully from the Claude Design handoff bundle: - /pipelines — stage bar, job-graph DAG with bezier connectors, syntax-highlighted log viewer - /releases — featured release hero (serif version, italic tagline), highlights, assets, verification box, past-release timeline - /admin — Forge overview: gauges, services, traffic chart, recent admin events - /admin/access — owner, collaborators, access tokens - /admin/storage — storage cards, snapshot timeline Shared Vue primitives extracted to /components: Icon (SVG icon library), Chip, Avi (gradient avatar), Sparkline, Kv (key-value), AdminNav. All pages use the design-system utility classes (.glass, .chip-*, .btn, .eyebrow, .mono, .serif) and tokens added in the prior PR. Data is hardcoded; backing APIs will land in follow-up extensions.
…row widths Below 640px the workspace sidebar is hidden and a fixed glass tab bar takes over as the primary nav. Five items: Home, Inbox, CI, Releases, Admin. Rounded 22px corners, backdrop-blur, safe-area-aware padding. Page content gets bottom padding so the tab bar doesn't overlap the last row. Topbar gets safe-area-inset-top so it doesn't sit under the notch on iOS.
Adds the 56px SideRail icon nav from the Claude Design handoff, sitting to the left of the workspace sidebar (VS Code-style activity bar + explorer pattern). Five primary icons (Home, Repositories, Inbox, Pipelines, Releases) and two footer icons (Forge admin, Settings), each with an accent rail indicator on the active route. The "Repositories" rail icon links to a new /repos page that lists every repo in the workspace as a grid of glass cards. The wide workspace sidebar collapses below 860px and both rails hide entirely below 640px, where the floating MobileTabBar takes over.
Replaces RepoHome view=code with a dedicated RepoCode route that mirrors the Claude Design handoff: repo header with tabs, branch + go-to-file sub-toolbar, 260px file tree, file viewer with breadcrumbs, last-commit strip, source/blame/history toolbar, syntax-highlighted code body (tok-key/str/num/cm/fn/ty/pn), and a co-pilot file digest panel.
New /r/:groups+/:repo/pulls/:id/review route mirroring the Claude Design handoff: PR header with stack progress strip, 280px stack rail with layer items + reviewers + checks, main diff pane with AI layer digest, syntax-highlighted diff rows (add/del marker columns, line-number gutters), inline conversation thread with co-pilot suggested resolution, /command compose bar, and layer-level approve controls.
New /r/:groups+/:repo/issues/board route mirroring the Claude Design handoff: serif page title, search box with kbd hint, saved views + new-issue buttons, 220px filter rail (state / type / milestones / people with avatars), List/Board/Timeline view toggle, milestone progress glass card with bar, and a card of issue rows with tag chips, state icon, comments, assignee avatars, reactions, and pinned-issue accent background.
Stronger glass panel — opaque rgba(14,16,20,0.86) background with blur(36px) saturate(180%) and a softer rgba(255,255,255,0.18) border plus three-layer box-shadow. Light-mode variant included. Group eyebrows now float without surface backgrounds or dividers (8px top margin). Active row gets rounded 8px corners with accent-soft background + accent-line border. Mark highlights switch from underlined-bold to accent-tinted background pills. Footer darkens to rgba(0,0,0,0.25) with tighter alignment.
Adds /r/:groups+/:repo/pipelines and /r/:groups+/:repo/releases mounting the existing Pipelines.vue and Releases.vue components. The repo header tabs in RepoCode (Overview / Code / PRs / Issues / CI / Releases) are now RouterLinks so the CI and Releases tabs jump to the per-repo pages.
Summary
v3 brings v1's load-bearing implementation pieces back into v2's kernel-shaped architecture:
See
V3_PLAN.mdfor the increment plan.Increment plan
crates/git-http)git http-backendshell adapterSPEC_COVERAGE.md,TODO.md,start.shEach increment ships as its own commit so the PR shows a real progression.
Test plan
🤖 Generated with Claude Code