Skip to content

feat: open a new project ready to work in - #2763

Draft
camielvs wants to merge 2 commits into
09-18-feat_connect_the_project_page_and_tangentfrom
09-21-feat_open_a_new_project_ready_to_work_in
Draft

camielvs wants to merge 2 commits into
09-18-feat_connect_the_project_page_and_tangentfrom
09-21-feat_open_a_new_project_ready_to_work_in

Conversation

@camielvs

@camielvs camielvs commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Projects → new project → Tangent is meant to be the way into the app, but the page it landed on was blank: no session, so the chat said there wasn't one; no tab, so the workarea said nothing was open; and nowhere for the caret to be. Everything had to be started by hand before any work could begin.

The rule

A project with no sessions gets one started on arrival, a pipeline opened beside it to work in — created and attached to the project first if it has none — and the caret in the composer.

The first two halves ask only whether the project has nothing in it, so a project someone has already worked in comes up exactly as they left it.

It also survives a second look. A session nobody typed into is detached again on unmount by discardEmptySession, so an untouched new project arrives session-less next time and is set up again — and finding the pipeline it attached last time is what stops that adding another.

This replaces useStartSessionWithPrompt, which did the same thing for the one case of a project carrying a starting prompt. That behaviour is unchanged: the prompt still opens the session, still names it Debug session, and is still cleared afterwards.

There is no such thing as a draft pipeline

The question was whether local storage could carry an empty one. It has to be a real file: useLoadSpecresolveSpecData throws Pipeline "x" not found for anything not in storage, so a tab pointed at a draft renders an error, not an empty canvas. The starter is therefore created through the same createNewPipeline the editor's File → New uses, writing the same default YAML.

Two things that needed care:

Naming it after the project means asking the right question. addFile checks the registry for uniqueness, but FolderIndexDbStorageDriver inherits write from the root driver and both write the same user_pipelines list — which also holds pipelines that never got a registry row. A name free by addFile's test can still be occupied, and writing it would overwrite that pipeline. availablePipelineName asks the stored-name list instead and suffixes 2, 3, … It also caps the length, since the name is recorded in the project's extra_data.

Its project row is written for both readers. Tangent's "Add a pipeline" records {type, identity}; the project page records {kind, storage, localName, localId} and reads it with claimsLocalPipeline. A row carrying only Tangent's shape still lists on the project page, but its removal dialog would say "This is the only copy, so deleting it here deletes it for good" — untrue of a pipeline that lives in the browser. So starterPipelineResourceInput composes both existing builders rather than picking one. browserPipelineTarget reads either shape back, so a pipeline added from the project page is found rather than duplicated.

The caret needed the embed to offer it

Focus was impossible from this side: the embedded chat took a session id, an agent id and an opening prompt, and <tangent-chat>'s internals come from the bundle the server serves. tangent-shell now has an autoFocus prop that focuses the composer once it accepts input and fires once per mounted session rather than taking focus back afterwards — so the three pinned packages move to f8435e6 and the Prime chat passes it.

The sub-agent tabs deliberately do not. Opening one is navigation, not an invitation to type, and the caret may be somewhere it was put on purpose.

Testing

pnpm run validate clean; 3045 tests pass, 27 new.

Covered against a mocked store: an empty project starts one session, creates one pipeline, attaches and opens it; a project with a session is left alone; a project with a pipeline already attached opens that one and creates nothing; a pointer-shaped row is recognised; the oldest of several is chosen; a failed session start does not go on to a pipeline; and the pipeline opens only after startSession resolves, because the workarea is keyed by session and a tab opened before there is one is silently dropped. Plus: the session composer asks for focus and the sub-agent tabs do not.

What could not be checked in a browser: all of it runs inside the Tangent page, which is unreachable in my environment and shows the box from #2760 — the same limitation as #2762 — and nothing on the project page changed, so there was no reachable surface to exercise.

One honest gap in the tests: the StrictMode replay that usePrepareEmptyProject's ref guard exists for cannot be reproduced in renderHook. I wrapped a test in StrictMode to try, measured that effects still ran once, and took the wrapper back out rather than leave coverage that looks real and isn't.

Worth a look on a configured Tangent: that the starter pipeline opens rather than erroring, that revisiting an untouched new project reuses it instead of adding a second, and that the composer actually holds the caret on arrival.

🤖 Generated with Claude Code

@camielvs
camielvs requested a review from a team as a code owner September 21, 2026 16:34
@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: 09-21-feat_open_a_new_project_ready_to_work_in/40b5200

camielvs commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

camielvs and others added 2 commits September 23, 2026 13:53
Projects -> new project -> Tangent is meant to be the way in, but the page it
landed on was blank: no session, so the chat said there was none; no tab, so the
workarea said nothing was open. Everything had to be started by hand before any
work could begin.

A project with no sessions now gets one started on arrival, with a pipeline
opened beside it to work in — created and attached to the project first if it
has none. Both halves ask only whether the project has nothing, so a project
someone has worked in comes up as they left it. A session nobody typed into is
detached again on unmount, so an untouched project arrives session-less a second
time and is set up again; finding the pipeline it attached last time is what
stops that adding another.

There is no draft pipeline to open — loading a spec throws for anything not in
storage — so the starter is a real file. Naming it after the project means
asking the stored pipelines whether that name is free: addFile asks only the
registry, while the driver it writes through keys off the stored-file list,
which also holds pipelines that never got a registry row.

Its project row is written for both readers of a browser-held pipeline. A row
carrying only Tangent's shape still lists on the project page, but would be
offered for deletion as though it held the only copy.

This replaces the auto-start that fired only for a project carrying a starting
prompt, and keeps that behaviour: the prompt still opens the session and is
still cleared afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A session opens for someone to type in, so the composer should already hold the
caret. There was no way to ask for that from here: the embedded chat exposed
only a session id, an agent id and an opening prompt.

tangent-shell adds an `autoFocus` prop that focuses the composer once it accepts
input, and fires once per mounted session rather than taking focus back
afterwards. Repointing the three pinned packages at that commit and passing it
is the whole change.

The sub-agent tabs do not ask for it. Opening one is navigation, not an
invitation to type, and the caret may be somewhere it was put on purpose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@camielvs
camielvs force-pushed the 09-18-feat_connect_the_project_page_and_tangent branch from 68939e0 to 5f534f6 Compare September 23, 2026 20:57
@camielvs
camielvs force-pushed the 09-21-feat_open_a_new_project_ready_to_work_in branch from 7149314 to 40b5200 Compare September 23, 2026 20:57

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant