Skip to content

feat(projects): fix cancelling, track shared projects, flag unreachable pipelines - #2790

Draft
camielvs wants to merge 7 commits into
09-22-feat_autofit_the_canvas_while_an_agent_editsfrom
09-22-feat_projects_that_can_be_shared_and_cancelled
Draft

camielvs wants to merge 7 commits into
09-22-feat_autofit_the_canvas_while_an_agent_editsfrom
09-22-feat_projects_that_can_be_shared_and_cancelled

Conversation

@camielvs

@camielvs camielvs commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Four things, all downstream of projects becoming shareable by URL.

1. The New Project dialog could not be cancelled

Not a veto — a layout shift. The Name field is autoFocused, so pressing the
mouse on Cancel blurred it, onBlur set nameTouched, and a ~50px <Alert>
mounted between mousedown and mouseup. DialogContent is
top-[50%] translate-y-[-50%], so the panel grew in both directions and the
footer slid out from under the cursor. A click needs mousedown and mouseup on
the same element, so none was ever produced. RenameProjectDialog was a
verbatim copy of the same pattern.

The complaint now waits for a press of Create — which is also what asks for it,
so Create stops being disabled while the name is empty (a disabled button fires
no submit, and the message would be unreachable).

Measured in a real browser, one Cancel click:

dialog still open afterwards
before true
after false

2. Resolving a local pipeline by name was a wrong-pipeline hazard

A pointer recorded a registry id, but resolution fell back to the pipeline's
name when the id did not resolve. Within one browser that is merely
imprecise. Across browsers it is wrong: a project is shared, the pointer lands
in a browser holding an unrelated pipeline of the same name, and the fallback
claims it.

  • where the resource recorded a name/ identity, that opened the wrong
    pipeline
    , silently;
  • where it recorded an id/ identity, the row read as available and then failed
    on click.

A recorded id is now the whole answer: an id that does not resolve means the
pipeline is not here. A pointer that recorded no id has only ever had its name,
so it is still resolved by name — but trusted only in the browser that wrote it,
which is resource.createdBy. Unknown authorship stays trusted, so single-user
backends are unaffected.

Two existing tests pinned the old fallback; both are replaced by tests asserting
the new rule, and the cross-browser collision is now covered directly.

3. Pipelines that cannot travel with the project listing them

Rows now read "Pipeline — not in this browser" and grey out, matching the
"Backend pipeline — not supported yet" row directly beside them. Remove still
works, because unopenable is not unremovable. Previously such a row opened a tab
that collapsed into a red error icon and filed a Bugsnag report for an entirely
expected situation.

Only a definite negative greys a row out — while the lookup runs, rows are left
alone rather than flickering.

4. Pinning, and a way out of Tangent

/projects only lists what the caller created, so a shared project was gone with
the link.

  • Pin a project from the card's own menu, or from either project header.
  • Pinning reorders rather than copying: a pinned project sorts to the front
    of the one grid and carries a pin in place of its folder icon. It stays an
    ordinary card — ⋮ menu, resource counts and dates all intact — and the list
    leaves out whatever is pinned, so each project is on screen exactly once. A
    project that arrived by a shared link was never in the list to begin with, and
    simply joins the front. "Showing N of M" follows.
  • The heading is now Projects rather than My Projects, since a pinned
    project may be someone else's.
  • Pins are local — the backend has no notion of who a project was shared with —
    so usePinnedProjects is deliberately the single seam to replace if it ever
    grows a server side. They ride on the existing favourites store, so a pinned
    project also appears under Favourites.
  • Visits are recorded too, but they feed the existing Recently viewed list
    rather than this page.
  • Tangent gains a back to projects link in the project header. The only way
    out was the logo, which leaves Tangent altogether rather than going up one
    level.

5. A deleted project that would not go away

Deleting a project left its links behind, and following one opened its Tangent
page with everything apparently intact.

  • The delete invalidated the detail query rather than removing it. An
    invalidated query keeps its data, so the next page to mount it was served the
    cached project in full and only then refetched its way to an error. It now
    removes it.
  • Recently Viewed and Favourites are local records the backend cannot prune, so
    the delete prunes them. Recent lists also update in the tab that wrote them —
    storage events only ever reached the other tabs.
  • The Tangent page never asked whether the project existed. A deleted one
    resolves no workspace to take a Tangent url from, so it sat on
    "Loading project…" indefinitely; it now says the project is gone and offers
    the way back.
  • Following a dead link no longer re-records it: a deleted project keeps its
    last-known name in the cache, which was enough to put it straight back on the
    list.

Verified against the local backend: created a project, opened it, deleted it
from its card, and confirmed it is absent from Recently Viewed and that its url
reads "Project not found" rather than spinning.

Testing

Full suite green — 313 files, 3178 passed.

Verified in the browser against the local backend: one-click cancel; the card
menu reading Pin project / Details / Share project / Delete project; a pinned
project moving to the head of the grid, appearing once, keeping its menu,
counts and dates, and carrying the pin marker; a genuinely absent local pipeline
greyed with its message; and the back link landing on /projects.

jsdom cannot catch item 1 at all — it has no layout, so userEvent.click
dispatches at the element reference wherever it has "moved".

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 22, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: 09-22-feat_projects_that_can_be_shared_and_cancelled/e5a678c

camielvs commented Sep 22, 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
camielvs force-pushed the 09-22-feat_projects_that_can_be_shared_and_cancelled branch 2 times, most recently from 29c5d18 to d31482b Compare September 23, 2026 00:32
@camielvs
camielvs force-pushed the 09-22-feat_autofit_the_canvas_while_an_agent_edits branch from a7d6d5d to 2284c78 Compare September 23, 2026 20:57
@camielvs
camielvs force-pushed the 09-22-feat_projects_that_can_be_shared_and_cancelled branch from 1007fa7 to 0847afe Compare September 23, 2026 20:57
@camielvs
camielvs force-pushed the 09-22-feat_autofit_the_canvas_while_an_agent_edits branch from 2284c78 to ddd8377 Compare September 23, 2026 22:01
@camielvs
camielvs force-pushed the 09-22-feat_projects_that_can_be_shared_and_cancelled branch from 0847afe to 817fb36 Compare September 23, 2026 22:01
@camielvs
camielvs force-pushed the 09-22-feat_autofit_the_canvas_while_an_agent_edits branch from ddd8377 to 4dce473 Compare September 23, 2026 23:00
@camielvs
camielvs force-pushed the 09-22-feat_projects_that_can_be_shared_and_cancelled branch 2 times, most recently from cbd78d9 to 37c0312 Compare September 23, 2026 23:36
@camielvs
camielvs force-pushed the 09-22-feat_autofit_the_canvas_while_an_agent_edits branch from 4dce473 to f5283a0 Compare September 23, 2026 23:36
@camielvs
camielvs force-pushed the 09-22-feat_projects_that_can_be_shared_and_cancelled branch from 37c0312 to 74c9c32 Compare September 23, 2026 23:56
@camielvs
camielvs force-pushed the 09-22-feat_autofit_the_canvas_while_an_agent_edits branch from f5283a0 to 0b3f9d7 Compare September 23, 2026 23:56
camielvs and others added 7 commits September 23, 2026 17:07
…le pipelines

Three things, all downstream of projects becoming shareable by URL.

The New Project dialog could not be cancelled. Blurring the autofocused
name field mounted the validation alert between mousedown and mouseup,
which grew the vertically-centred dialog and moved Cancel out from under
the pointer, so no click was ever produced. The complaint now waits for a
press of Create, which is also what asks for it, so Create stops being
disabled while the name is empty. Rename had a verbatim copy of both.

A shared project was unfindable once the link was gone, because the
projects page asks the backend for the ones the caller created and
nothing recorded a visit. Opening a project now records it, starring one
keeps it past the hundred-visit cap, and the projects page grows a
"Shared with me" section for the ones somebody else made. All local:
the backend has no notion of who a project was shared with, so
useSharedProjects is the single seam to replace if it ever does.

A browser-held pipeline cannot travel with the project that lists it. Its
row looked ordinary and fully clickable, and opening it failed deep in
useLoadSpec, leaving a red error icon and a Bugsnag report for an
entirely expected situation. Rows now say "Pipeline — not in this
browser" and grey out, on whether this browser actually holds the
pipeline rather than on who added it — which also covers your own
pipeline seen from a second device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e, add a way out of Tangent

A recorded registry id was tried first but fell back to the name, which
across browsers is a coincidence rather than a match: a project is
shared, the pointer arrives at a browser holding an unrelated pipeline of
the same name, and the name fallback claims it. Where the resource
recorded a name-identity that opened the wrong pipeline outright; where
it recorded an id it reported the row as available and then failed on
click. An id is now the whole answer, and a pointer that recorded no id
is trusted by name only in the browser that wrote it.

The projects page now carries a Pinned section above My Projects, driven
by pins alone rather than by pins and visits together, and projects are
pinned and unpinned from the card's own menu as well as from the two
project headers. Visits are still recorded, but they feed the existing
Recently viewed list rather than this page.

Tangent had no way back to the projects list except the logo, which
leaves Tangent altogether.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…strip

A pinned project of the caller's own appeared twice on one screen, once
under Pinned and again in My Projects. Pinning now moves a project rather
than copying it: the list leaves out whatever is pinned, so each project
is on screen once whether it was pinned from the list or arrived by a
shared link and was never in the list at all. The list's count follows.

Pinned is also no longer a second wall of cards. It is a row of chips
around 38px tall — folder, name, what is in it, and an unpin — so the
section costs a line rather than a card row however few things are in it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The compact strip made a pinned project a second-class copy of itself: no
menu, no resource counts, no dates. Pinned projects are ordinary cards
again, sorted to the front of the single grid and marked with a pin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…paged in

The tail count added every pinned project to the total, so a pinned project
of the user's own inflated the total by one until its page was fetched.
Authorship answers it without the page: the user's own are already counted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deleting a project left its links behind: it stayed in Recently Viewed, and
following one opened its Tangent page with everything apparently intact.

- The delete invalidated the detail query instead of removing it, so the next
  page to mount it was served the cached project in full and only then
  refetched its way to an error.
- Recently Viewed and Favourites are local records the backend cannot prune,
  so the delete prunes them. Recent lists now also update in the tab that
  wrote them, rather than only in the others.
- The Tangent page never asked whether the project existed. A deleted one has
  no workspace to take a Tangent url from, so it waited on a spinner forever.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@camielvs
camielvs force-pushed the 09-22-feat_autofit_the_canvas_while_an_agent_edits branch from 0b3f9d7 to 67a04b0 Compare September 24, 2026 00:14
@camielvs
camielvs force-pushed the 09-22-feat_projects_that_can_be_shared_and_cancelled branch from 74c9c32 to e5a678c Compare September 24, 2026 00:14

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