Files
clide/governance/questions/architecture.md
T
jpmschweitzerandClaude Opus 4.8 bb0d78f8de Q-51 + T-422 epic: unify workspace lifecycle on one fenced primitive
The status-bar branch bleed (T-421) is a symptom of a deeper gap: there
is no single "open workspace X" primitive — only project.open() (in-place,
same process, shared daemonBus) and newWindow() (blank detached process,
no repo arg, no env scrub). T-367 and T-269 are the same root.

- Q-51 (architecture): unify on WorkspaceService.open(root, target);
  open question of whether in-place switching survives at all vs a
  strict workspace⇒window⇒process⇒socket⇒bus⇒session-id 1:1 mapping.
- T-422 epic owns the unification; T-421 reparented under it; T-423
  builds the primitive and routes all entry points through it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:47:33 +02:00

26 KiB

Open Questions — Architecture

IPC, events, canvas, window chrome, macOS signing, pql absorption, ticket persistence.


Q-1: Authorisation granularity on the IPC socket

  • Status: Open
  • Question: The daemon's token auth is coarse (allow all / deny all). Do we need per-subsystem grants later (e.g. restrict git push), and if so, what's the model — capability tokens? An explicit grant table per client? Time-limited grants?
  • Context: Surfaced in the old ADR 0006 open-questions footer; deferred until Tier 1 is in real use.
  • Triage (2026-05-17): Still open. Tier 1 has shipped but the IPC socket server itself is unimplemented (T-99). Re-evaluate once the socket lands and external CLI clients exist.
  • Source: ADR 0006 (migrated to D-6).

Q-2: Back-pressure on event streams

  • Status: Resolved → D-85
  • Resolved (2026-06-06): Bounded per-subscriber ring buffer, drop-oldest, with a per-subscriber dropped-count surfaced as a gap marker — producer never blocks, subscribers never killed. The ring also serves the Q-3 cursor retention.
  • Question: A subscriber that falls behind on pane.output (a firehose) needs a policy: drop oldest, block producer, coalesce, or kill subscriber. Which?
  • Context: The event bus is in-memory; back-pressure policy is undefined. Defer until Tier 1 is in real use and we have a real firehose to measure against.
  • Triage (2026-05-17): Still open. PTY panes ship and produce real firehoses, but no subscriber has fallen behind in observed use. Re-evaluate when a multi-client IPC scenario (T-99) makes this measurable.
  • Source: ADR 0006 (migrated to D-6).

Q-3: Event persistence + audit/undo

  • Status: Resolved → D-85
  • Resolved (2026-06-06): In-memory cursor ring only in v1 (no disk); serves clide events --since. If persistence is ever needed it is owned by the bus, not a subscriber-subsystem — reversing ADR 0006's unreasoned open-questions footer (it asserted "a subsystem that subscribes and writes — not a property of the bus" with no rationale, which is why it became this question).
  • Question: Events are in-memory only in v1. If a future need (audit log, undo history) wants persistence, is it a property of the bus or a subsystem that subscribes and writes?
  • Context: ADR 0006 leaned "subsystem that subscribes and writes" but didn't commit.
  • Triage (2026-05-17): Still open; no concrete trigger yet. Revisit when the first persistence requirement lands (likely Tier-6 audit/undo).
  • Source: ADR 0006 (migrated to D-6).

Q-4: .canvas schema compatibility with Obsidian

  • Status: Open — narrowed by D-91
  • Question: Clide's canvas (Tier 5) should read/write something — either Obsidian's .canvas JSON schema verbatim, a compatible-ish superset, or our own format. Each has trade-offs.
  • Context: Obsidian's canvas users might want their canvases portable; conversely, bending to Obsidian's schema constrains our canvas features.
  • Narrowed (2026-06-10): D-91 sets the direction — clide's canvas uses its OWN HTML-<canvas>-inspired JSON (the unified drawing-card schema), not Obsidian's .canvas natively. Obsidian .canvas is treated as an import format, ingested by converting it into clide's JSON, so the same renderer can display it. What remains open here: the exact converter mapping and how lossless/round-trippable .canvas interop needs to be.
  • Source: CLAUDE.md "Open questions" footer.

Q-5: IPC wire-format stability + schema_version:

  • Status: Open
  • Question: When do we freeze the IPC envelope / schema and introduce schema_version: in pubspec.yaml? What's the bump policy for breaking changes?
  • Context: Covered partially by D-6's v: 1 starting point; CLAUDE.md flags this as "decide when the first real subcommand lands."
  • Source: CLAUDE.md "Open questions" footer.

Q-6: Window chrome — native frame vs frameless custom

  • Status: Resolved → D-57
  • Question: Does clide ship with the OS-native window frame (title bar, min/max/close from the WM) or a frameless custom chrome that gives us pixel control at the cost of reimplementing window controls per-platform?
  • Context: Surfaced during Tier-0 plumbing discussion; resolved 2026-04-23 — frameless with per-column 24px hats.
  • Source: 2026-04-21 planning.

Q-7: macOS app bundle signing / notarisation

  • Status: Open
  • Question: Distributing a signed macOS .app requires a Developer ID and a notarisation pipeline. Do we gate macOS builds on this (Tier 6), or ship unsigned with a known "right-click, open" user workflow for early testers?
  • Context: Linux is primary; macOS is a stretch target. Notarisation is a separate cost from the Flutter build.
  • Source: 2026-04-21 planning.

Q-21: Pql absorbs planning vs keeps separate

  • Status: Resolved → D-3 + D-39
  • Question: Three shapes for planning tooling's long-term home: (A) Pql absorbs planning — pql decisions … + pql ticket … subcommands; clide shells out. (B) Clide absorbs pql — reverse D-3, one big Dart tool. (C) Separate new binary just for planning.
  • Context: Resolved 2026-05-11 in favour of (A). pql 1.4.30 ships full planning surface (pql decisions …, pql ticket …, pql plan …). Clide consumes via shell-out under lib/src/pql/. D-39 already encoded the intent; D-3 the wrap-don't-duplicate rule. The Python stopgap (D-40) was sunset on schedule.
  • Source: 2026-04-21 planning.

Q-23: SSH-remote development — run clide against a remote workspace

  • Status: Resolved → D-96, D-97, D-98, D-99

  • Resolved (2026-06-12): Shape (A) — execution remote, UI local — with the no-install ssh-exec footprint (user pick): stock OpenSSH only, ssh -tt PTYs + ControlMaster exec channels, polling watcher, zero clide components on the remote (D-96). Naming/auth via ssh:// URI + system ssh in BatchMode (D-97); remote-tool contract with batched connect preflight (D-98); session + state identity re-keyed on (host, repo), amending D-41/D-77 (D-99). Implementation: T-329 epic, execution layer T-336.

  • Question: Clide today assumes the workspace, the daemon, and the Flutter UI all run on the same machine. A growing class of users edits on remote systems (build servers, GPU boxes, cloud dev environments). What's the architecture for "open repo on host-B from UI on host-A"? Two shapes: (A) daemon-on-remote — clide's Dart daemon runs on the remote; the app talks to it over an SSH-tunnelled unix socket or a dedicated TCP socket (mTLS?), pty/process/filesystem work stays server-side; local app is pure UI. (B) filesystem-mounted — remote mounted via sshfs/9p/rclone, daemon runs locally against the mount; simpler but every fs op + git call crosses the network, and PTYs get complicated (local shell on remote filesystem? ssh-exec per command?). (A) matches VS Code Remote / JetBrains Gateway; (B) matches nothing load-bearing. Sub-questions either way: auth (ssh-agent? per-project keys? OIDC?), tmux / Claude session persistence semantics (does primary-per-repo re-key on host + repo?), multi-host identity in .pql/pql.db, latency tolerance for the event stream, re-sync on disconnect.

  • Context: Surfaced 2026-04-22 during Tier-1 planning. Not a Tier 1 concern — terminal + Claude panes land local-first — but the daemon/IPC seam decisions (notably D-5 and D-6) constrain the future answer. Worth scoping before Tier 6 (extension API) so third-party extensions don't accrue assumptions the remote path would have to unwind. The 2026-06-11 Fable review (fable-ous.md Part IV, Tier 3 #14) reframed the differentiator and ranked T-329 as undersold: the agent runs on the buildbox while permission prompts render natively local — VS Code Remote moves the editor; nobody remotes the agent control channel. That framing favours shape (A).

  • Triage (2026-06-12): Shape (A) is effectively settled (T-329 epic locked: execution remote, UI + clipboard local, system ssh auth for v1). The model-independent backbone is proceeding: Phase 1 (T-331, DaemonTransport seam) landed. What remains open is the footprint model — the user decision T-330 gates on:

    1. No-install ssh-exec — zero remote footprint; stock ssh -tt PTYs + ControlMaster command channels; watching degrades to polling; heavier subsystem surface locally.
    2. Auto-pushed self-managed agent (VS Code Remote model) — clide deploys/version-checks a headless agent binary on connect; native inotify + stateful backend; but installs clide components on the remote, which the user has said they don't want to manage.

    If (2), the agent sub-questions need answers before T-336 expands: placement (per-host ~/.clide/agent likely), GC on disconnect/version-bump/repo-removal, multi-client sharing (IpcServer is already multi-connection, D-72), version skew (version-named binaries coexist). Either way the remote-tool contract needs a D-record: what must exist remotely (git/shell at minimum), whether pql is hard-required or degrades, and how a connect-preflight surfaces gaps. Evidence gap: the ControlMaster per-command latency probe (T-330) needs a reachable sshd — none in the dev environment; run it against a real remote before deciding if latency is the deciding factor.

  • Source: 2026-04-22 planning (user-raised).

Q-22: Ticket persistence strategy

  • Status: Resolved → D-67
  • Question: Once Q-21 resolves in favour of (A), how do tickets handle shared team state? (1) Never commit (per-dev, ephemeral — works for solo). (2) Commit on milestone (settled-reach's sprint-close pattern — kanban has no natural equivalent, release or tier-cut is the closest). (3) Markdown mirror — every mutation writes tickets/T-NNN.md alongside SQLite; git-legible authoritative record; DB is rebuildable. (3) is probably the eventual answer.
  • Context: Resolved 2026-05-11 → option (3), evolved. Pql 1.4.x reshaped ticket persistence into append-only per-month .pql/changelog/<table>/<YYYY-MM>.sql files with inline LWW guards. Committed alongside code; pql.db rebuildable from changelog + governance/*.md. Clide migrated on 2026-05-09.
  • Source: 2026-04-21 planning.

Q-25: Body text face — mono everywhere vs Josefin Sans UI + mono code

  • Status: Open
  • Question: The design handoff uses JetBrains Mono for all UI text (tab labels, file paths, status bar, sidebar labels), reserving Josefin Sans only for display/title text. Our current implementation uses Josefin Sans as the ambient UI face with JetBrains Mono only for code/terminal/diff surfaces. Which direction?
  • Context: The design's "mono everywhere" rationale: clide is an IDE for people who like grids. The current Josefin Sans rationale: visual distinction between chrome text and code text, warmer feel. Both are valid — this is a feel decision, not a technical one.
  • Triage (2026-05-17): Still open. The Josefin-Sans-as-UI-face implementation has shipped and is the current default; the design's "mono everywhere" direction remains unrealised. Convert to a D-record when the design call is made.
  • Source: 2026-04-22 design handoff review.

Q-26: Small screen layout (< 1000px)

  • Status: Open
  • Question: Below 1000px window width, should clide switch to modal viewer/editor instead of split, or stack panels vertically? The spec defers this but flags it.
  • Context: The interaction model defines breakpoints down to 1200px but punts on < 1000px. This matters for small laptops and tiling WM users who give clide half a screen.
  • Source: 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 1.

Q-27: Two-editor split

  • Status: Open
  • Question: Should clide support two files open in the editor simultaneously (horizontal split in the middle column)? The spec says resist until proven needed — feels like tabs creeping back.
  • Context: D-48 deletes buffer tabs. A two-editor split would be the only way to compare files side-by-side without using the viewer ↔ editor swap. The diff view may cover most of this need.
  • Source: 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 2.

Q-28: Terminal strip scope — shell only or logs/errors/tests

  • Status: Resolved → D-87
  • Resolved (2026-06-06): Split by interaction. A bottom output dock hosts read-only output — logs + problems — toggled from a merged health/log status-bar widget (D-87). The terminal is NOT in the strip: it's an interactive surface, kept first-class in the editor pane (D-49). So "both, later" became "logs/problems yes, terminal no."
  • Question: Is the app strip (bottom bar) purely a terminal shell + status, or does it also host tabs for logs, errors, and test output? Probably both, later.
  • Context: The interaction model spec defines the app strip as 14px with terminal shell + daemon indicator + branch; expanding on focus. If it grows to host logs/errors/tests, it becomes a mini-panel with its own tab model.
  • Source: 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 3.

Q-29: Branch picker location

  • Status: Open
  • Question: The branch picker was moved out of the bottom status bar. Best place is inside the git section header (left panel), with a compact indicator in the app strip. Confirm or revise?
  • Context: The interaction model spec suggests the git section header as the primary location. The app strip shows a compact indicator (branch name only) for at-a-glance awareness.
  • Source: 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 4.

Q-30: Focus behavior when editor is dirty and viewer is peeked

  • Status: Open
  • Question: When the editor has unsaved changes and the user peeks a viewer, where does focus land? The spec says prompt-bar-rule wins: focus stays in Claude.
  • Context: This intersects D-47 (Claude is home) and D-49 (editor mode). If focus always snaps to Claude, the user must explicitly re-focus the editor to continue typing.
  • Source: 2026-04-22 interaction model spec (Wireframe — Flows v3), open question 5.

Q-31: XWayland fallback for frameless — proper Wayland protocol needed

  • Status: Open (load-bearing workaround in place)

  • Question: The frameless window (D-57) currently forces GDK_BACKEND=x11 because GTK3 doesn't implement the xdg-decoration Wayland protocol and KWin ignores gtk_window_set_decorated(FALSE) on native Wayland. When do we replace this with a proper implementation?

  • Context: The XWayland fallback works but has tradeoffs: one extra buffer copy per frame, degraded fractional-scaling on HiDPI (125%/150% gets blurry), loss of native Wayland touchpad gestures and per-window DPI, and slightly less reliable cross-app drag-and-drop. For an IDE these are tolerable but not ideal.

    Current workaround (Makefile):

    GDK_BACKEND=x11 flutter run -d linux
    

    Also set in the native runner: gtk_window_set_decorated(window, FALSE) + gdk_window_set_decorations(gdk_win, 0) in linux/runner/my_application.cc.

    Rollback if unstable: Remove GDK_BACKEND=x11 from the Makefile run target. The OS title bar returns but the app runs on native Wayland. Our hat bar renders below the OS bar (double chrome) until the proper fix lands.

    Proper fix — bypass GTK, talk libwayland-client directly:

    1. Link libwayland-client in linux/runner/CMakeLists.txt.
    2. Generate protocol headers from xdg-decoration-unstable-v1.xml (ships with wayland-protocols package) via wayland-scanner.
    3. In my_application.cc after window realize: get the wl_display via gdk_wayland_display_get_wl_display(), bind to zxdg_decoration_manager_v1 from the registry, get the toplevel decoration object, call zxdg_toplevel_decoration_v1_set_mode(deco, MODE_CLIENT_SIDE).
    4. ~100 lines C total. Needs wayland-protocols as a build dep.
    5. The GDK_BACKEND=x11 env var is then removed.

    Alternative timeline: Flutter moves to GTK4 (which has built-in xdg-decoration support). Track Flutter issue #94381. When it ships, drop all native decoration code and use gtk_window_set_decorated(FALSE) — it will just work.

  • Source: 2026-04-23 D-57 implementation.

Q-32: MCP tool surface — minimum slash-ide or extended clide tools?

  • Status: Resolved → D-86
  • Resolved (2026-06-06): Full mcp__clide__* namespace, but generated from the co-registered command registry (D-74) that already feeds the CLI + palette — so breadth costs no hand-maintained second surface, and the extensions-first model needs registry-driven surfacing anyway. Per-command MCP opt-out for poor-fit verbs.
  • Question: D-68 commits clide to an /ide-compatible MCP server. The minimum surface is the two tools Claude Code's /ide integration currently expects: mcp__ide__getDiagnostics (lint/diagnostics for a file) and mcp__ide__executeCode (run code in a Jupyter kernel). Do we stop there, or also expose a mcp__clide__* namespace with higher-leverage tools (open_file, goto_symbol, pql_query, pane_spawn, git_status, …) so MCP clients other than Claude Code (Cursor, Windsurf, VS Code Copilot) can drive clide as a real backend?
  • Context: The minimum surface keeps clide a good citizen in the /ide ecosystem and avoids duplicating the CLI in MCP form. The extended surface would let non-Claude-Code MCP clients integrate richly, but invites surface bloat (every CLI verb tempted to gain an MCP twin) and a maintenance second front. Note that for Claude-Code-in-a-clide-pane, the CLI surface already covers this — extended MCP tools serve external MCP clients only.
  • Source: D-68.

Q-33: MCP transport — SSE, WebSocket, stdio, or all?

  • Status: Resolved → D-73
  • Resolved (2026-05-19, drift-fixed 2026-06-06): SSE over HTTP only. Closed by D-73 (and confirmed in D-68's amendment) at the time, but this index was never flipped from Open. Re-confirmed 2026-06-06: stdio/WebSocket not added — D-73's reasoning (the always-running GUI is connected to, not spawned) still holds and no external process-spawn MCP client is a concrete need yet.
  • Question: Claude Code's /ide integration connects via SSE-IDE or WS-IDE (URL passed at startup). MCP also supports stdio for process-spawn clients. Which transport(s) should clide's MCP server expose — SSE only (the most common /ide server pattern), SSE + WS (broader compatibility), or all three including stdio?
  • Context: Transport choice affects discovery and lifecycle. SSE/WS need a port and a published URL, which collides with the XDG_RUNTIME_DIR Unix-socket model used for the CLI; we'd likely publish the URL alongside the socket path (env var or XDG_RUNTIME_DIR discovery file). stdio is process-per-client and works for clients that prefer process-spawn over network. Decision interacts with Q-32 — if the surface stays at the /ide minimum, SSE alone is sufficient.
  • Source: D-68.

Q-34: How + when to surface the account/team token budget given upstream doesn't expose it

  • Status: Open
  • Question: The team-meta sidebar should show the account/subscription usage budget (5-hour + weekly limits, % used, reset times), but Claude Code does not expose this data programmatically under subscription (OAuth) auth (verified 2026-05-23, claude 2.1.150): /usage is TUI-only, stats-cache.json holds activity counts only, the stream-json rate_limit_event is undocumented and needs a billed turn, and claude auth status --json shows plan only. So: what path do we take, and when do we act? Options — (a) wait for upstream claude usage --json / a /v1/organizations/{org}/usage/subscription endpoint (open feature request anthropics/claude-code#44328); (b) an API-key usage path for key-auth users only; (c) scrape the undocumented rate_limit_event on a billed turn (fragile, version-pinned); (d) ship a partial budget (activity counts) and defer true limits.
  • Context: The only unshipped piece of the otherwise-complete native-Claude epic (T-132). Blocked on data availability, not on clide work — hence a question (when/how to revisit) rather than active scope. Option (c) interacts with D-75's "version-pinned coupling to CC internals" posture. Resolved by T-158 when a viable path lands.
  • Source: 2026-06-09 — split out of T-132 / T-158 (was "blocked on upstream"); project memory claude-usage-budget-not-exposed, GitHub anthropics/claude-code#44328.

Q-50: Web/WASM target after the dart:ffi pivot — fence, fix, or drop?

  • Status: Open
  • Question: flutter build web --wasm no longer compiles: the tree-sitter FFI pivot and the native PTY both import dart:ffi unconditionally, which the wasm target forbids. That kills make test-e2e / ui-dev / ui-smoke and the Playwright harness regardless of the cd app staleness T-384 fixed. Options: (a) fence every dart:ffi import behind conditional imports with web stubs (ongoing tax on every future native binding, for a target CLAUDE.md calls "a happy accident"); (b) keep the harness parked and re-evaluate if/when a web build matters (D-26's Playwright driver stays dormant); (c) drop the web target + tools/ui/ harness formally and amend D-26/D-32. The guardrail says don't compromise desktop fidelity for web — (a) leans against it; (b) defers; (c) is honest but irreversible-ish.
  • Context: Surfaced 2026-06-12 while fixing T-384 (dead make targets). The mechanical path fixes (post app/-flattening) are done; the Gitea workflow's e2e job is withheld with a pointer here. The startup-regression gate (D-27) and integration tests are unaffected — only the browser/Playwright surface is blocked.
  • Source: T-384 / 2026-06-11 Fable review (epic T-359).

Q-51: Unify workspace lifecycle on a single fenced open primitive

  • Status: Open
  • Question: There is no single "open workspace X" primitive — only two half-primitives in different layers. project.open(root) (lib/kernel/src/project.dart) is the only repo-targeting path and is intrinsically in-place: it rebuilds services in the same process, reusing the shared daemonBus. newWindow() (lib/builtin/menubar/src/file_actions.dart) spawns a blank detached Process.start with no repo argument and no env scrubbing. To open a repo in a new window you must spawn a blank window and then run the in-place switch inside it. Should both fold behind one WorkspaceService.open(root, {target: thisWindow | newWindow}) that is the sole deriver of IPC identity from a root — and, more fundamentally, should in-place switching survive at all, or should workspace ⇒ window ⇒ process ⇒ socket ⇒ bus ⇒ session-id be strictly one-to-one so the leak/bleed class becomes structurally impossible?
  • Context: Surfaced 2026-06-14 from T-421 (status-bar branch bleeds across parallel windows). The same root cause — scattered, per-entry-point workspace lifecycle with no single fencing owner — already produced T-367 (in-place switch leaked the entire previous service set) and T-269 (kept the previous repo's Claude session). If in-place switching is abolished, the teardown burden those tickets patch disappears entirely. Relevant decisions: D-70 (per-workspace socket path), D-56 (one server per workspace), D-72 (multi-connection serial dispatch).
  • Source: T-421 / 2026-06-14 user review.