Consolidate Docs / Tasks / repo IDE into lenses on workspaces
Follow-up from the workspace namespace revamp (PR #2373) and the 2026-07-31
jam-flow discussion with Jonas. Now that a workspace is a private working
copy of the project's one path namespace (every repo auto-mounted at its own
/repos/** path, private files under the workspace's own path), the apps
that render files should stop owning file concepts and become lenses on a
workspace:
- Docs app — already a lens:
(workspace, file)in the URL, comments and edits write into that workspace, no nouns of its own. The model to copy. - Tasks app — almost a lens: a board is really
(workspace, repo scope), but hides it behind "checkout", a client-minted random id whose only remaining job is naming a throwaway workspace (apps/tasks/src/lib/checkout-shared.ts—checkoutWorkspacePath,newCheckoutId). With derived mounts those minted workspaces buy nothing:/repos/config/tasks/**resolves identically in every workspace. - Repo IDE (
apps/os/src/components/repo-ide/) — the richest file UI we have (file tree, editor pane with change gutter, staged-changes model, commit diff pane, commit history, markdown frontmatter + HTML preview, TypeScript vfs/diagnostics) but bound directly to a REPO, so it cannot show a workspace's uncommitted overlay at all.
Goal#
One shared vocabulary: a workspace is the collaboration surface; every
file UI is a lens over it. Extract the reusable repo-IDE components
(file tree, editor pane + change gutter, staged-changes/commit controls,
diff + history panes, previews) into a shared package alongside the pieces
Docs/Tasks already share (@iterate-com/workspace-documents: comments,
identity, collab), parameterized by the workspace fs surface
(readFile/readFiles/glob/status/readBase/collab) instead of repo RPCs. Then:
- Docs app = the document lens (single file + annotated-markdown comments).
- Tasks app = the board lens (
(workspace, repo); glob**/tasks/**/*.md). - Repo IDE = the tree/editor lens — pointed at a workspace it gains
uncommitted-overlay display for free (
status+readBasealready exist).
Work items#
- Tasks app addresses
(workspacePath, repoPath). New doorworkspaceAt(workspacePath, repoPath)next to the checkout one (no lazy-create, no checkout-index announce — mirror the Docs app's plain-getposture), route accepts?workspace=, and atasks.link({ workspace, repo, task? })config-bridge RPC + template getter + one agent-prompt line so agents can mint board links the way they mint Docs review links. DONE 2026-07-31:workspaceAtdoor +/w?workspace=route (/w/$idstays for the app's own boards),TasksAppRpcTarget.linkin the tasks config bridge,get tasks()in the config-repo template, one prompt line (budget ceiling 4100 → 4150, argued in the PR). - Guest mode for foreign workspaces. A lens on a workspace you don't
own gets read/comment/edit — never Commit or Discard-all (the board's
commit publishes the mount's ENTIRE dirty set; discard wipes the agent's
uncommitted work mid-thought). Publishing stays the workspace owner's
act: you tell the agent to ship.
DONE 2026-07-31: ownership = path shape (nothing durable records an
owner; the platform's
workspace/createdpayload is empty). The app owns only its own/workspaces/tasks/naming; every other workspace is a guest lens — Commit/Discard-all/Assign-agent hidden in the UI AND refused at the vessel door. - Extract the repo-IDE components into the shared package and re-point the repo IDE itself at the workspace surface (a repo view is then just a lens on any workspace, scoped to that repo's mount). Scoped by the 2026-07-31 /goal below: the tree + diff viewer are the pieces to extract first, into the combined app's shell.
- Retire "checkout". Delete the dead Y.Doc lane
(
TasksCheckoutApi,checkout-do.ts,/c/$checkoutId,/collab/$checkoutId; portassignAgentto the workspace lane first), then replace the checkout index DO with the platform's enumerable workspace list. DONE 2026-07-31:assignAgentported toTasksWorkspaceApi(owner act), Y.Doc lane +/yjsupgrade + both DOs deleted (tombstoned in wrangler.jsonc), sidebar/home list workspaces viastreams.list()filtered to/workspaces/**(board paths parse back via the embedded repo hash; agent workspaces appear as guest lenses). - Branch-targeted commits — split out to workspace-branch-targeted-commits (separate platform follow-up; not part of the lens work).
Decisions already taken (2026-07-31, Jonas)#
- Consolidate around workspace as the one noun; apps are lenses.
- A jam session happens in the agent's own workspace; humans join as guests through lenses (no neutral "session workspace" noun).
- Per-commit branch targeting is wanted, as a separate follow-up from the lens consolidation.
Direction update (2026-07-31, Jonas via /goal)#
Docs and Tasks should ultimately combine into ONE app whose top-level noun is the workspace:
- At the highest level you select a workspace path (or make a new one).
- In a workspace you see all the repos (main branch, via the derived mounts) plus the workspace's own non-repo files; unsaved changes can span multiple repos; committing a repo commits that mount's changes to main.
- A pierre-style tree viewer explores all files (reuse the apps/os repo-IDE tree and diff viewer — the item-3 extraction feeds this).
- The doc editor and the kanban tasks view are just VIEWS on that data.
- The viewer can be constrained to a root folder (e.g.
/repos/config) to mimic today's tasks app.
The item-1/2/4 work above is the substrate (workspace addressing, guest posture, workspace enumeration, no app-owned storage); the combined app grows from apps/tasks. Remaining: the workspace-level tree + file views (item 3's extraction), folding the Docs single-document view in, and then retiring apps/docs behind a link redirect.
DONE (2026-08-03): one app, two views. The combined app grew from
apps/docs, not apps/tasks: the board moved wholesale into apps/docs as the
/w route (docs and tasks are two views in ONE app behind ONE host —
docs--<slug>), the sidebar switches views with plain in-app links, the
vessel serves two capnweb roots (/api docs lane, /api/tasks board lane),
@iterate-com/docs now exports both config bridges (TasksApp is
link-capability only — tasks.link mints /w URLs on the docs host), and
apps/tasks is deleted along with the tasks-- host branch in the template
(no backwards compatibility, per Jonas). The deployed tasks vessels stay
parked (workers are never deleted). Sequel (same day): the API unified too —
ONE capnweb root at /api (the /api/tasks second root died), one authenticate
returning one project surface carrying both lenses (workspace() = document
lane, workspaceAt()/board() = board lanes, one board-aware workspaces()),
one browser WebSocket under both views, and itx.worker.tasks deleted —
itx.worker.docs.link mints either view by input shape ({workspace, path}
docs, {workspace, repo, task?} board). Remaining from the /goal list: the
pierre-style tree + diff views (item 3's extraction) and the root-folder
constraint.
DONE (2026-09-09): the workspace is the tree. A workspace is its path and
nothing else: the board is a view on ANY workspace (/w?workspace=<path>&repo=),
the minted /workspaces/tasks/<id>~<repo-hash> naming is gone, and "New
workspace" on the board home is an ordinary scratch workspace opened on a
repo's task files; the Docs vessel forwards the platform
workspace surface verbatim (fs, git, collab as sub-targets) and the shared
transport type is that surface; the Docs view shows the WHOLE workspace
(every mount plus the own directory) with listings loaded per mount and one
Commit control per dirty mount; the tree, files hook, read-only file view,
diff-against-HEAD (CodeMirror merge view), commit controls and file kinds
live in @iterate-com/workspace-documents; apps/os renders them at
/projects/<slug>/workspaces/<path> over the live itx stub, with an "Open
workspace" link on the agent details sheet. Platform: itx.workspaces.list()
and prefix-scoped globs. Remaining: the collaborative document page and the
board are still Docs-only; a jam still births a second workspace for its
agent (the recorded decision — jam in the agent's own workspace — is not
implemented); a readDir for per-folder lazy listing inside a big mount.
Platform follow-up: a real workspaces.list()#
The picker currently enumerates via streams.list() filtered to
/workspaces/**, pruning entries that are strict path-prefixes of others
(ancestor-announcement phantoms — verified on prd 2026-07-31: a nested
agent workspace drags 3 never-created ancestor streams into the catalog).
The honest fix is the sandbox pattern: teach the project reducer
workspace/created (via recordDomainObject, like repos/secrets/devices)
and add itx.workspaces.list(); then the vessel filter and the prefix
heuristic both delete.
Landed 2026-09-09: the birth batch's workspace-catalog subscription copies
workspace/created to /, the project reducer records it via
recordDomainObject, and itx.workspaces.list() reads state.workspaces.
Decided 2026-09-09 (Jonas): no ownership, no jam. Nothing records or cares
who a workspace belongs to — Commit is available on every workspace, agents'
own included. The /jam route, Invite AI, and the scratch namespace are gone;
"New workspace" in the sidebar names a workspace (pre-filled with three random
words) at /workspaces/<name>.