Conversation
🎩 PreviewA preview build has been created at: |
camielvs
force-pushed
the
09-17-feat_project_page
branch
from
September 18, 2026 21:19
d70b987 to
1e2f163
Compare
camielvs
force-pushed
the
09-18-feat_local_pipelines_in_projects
branch
from
September 18, 2026 21:19
694dfab to
b3ac3e8
Compare
camielvs
force-pushed
the
09-17-feat_project_page
branch
from
September 18, 2026 21:26
1e2f163 to
501de46
Compare
camielvs
force-pushed
the
09-18-feat_local_pipelines_in_projects
branch
from
September 18, 2026 21:26
b3ac3e8 to
b59af04
Compare
camielvs
force-pushed
the
09-17-feat_project_page
branch
from
September 18, 2026 21:45
501de46 to
98b2313
Compare
camielvs
force-pushed
the
09-18-feat_local_pipelines_in_projects
branch
from
September 18, 2026 21:45
b59af04 to
3689e2f
Compare
camielvs
force-pushed
the
09-17-feat_project_page
branch
from
September 18, 2026 22:09
98b2313 to
74d9257
Compare
camielvs
force-pushed
the
09-18-feat_local_pipelines_in_projects
branch
from
September 18, 2026 22:09
3689e2f to
53a3efb
Compare
Draft
Collaborator
Author
camielvs
marked this pull request as draft
September 18, 2026 22:19
This was referenced Sep 18, 2026
This was referenced Sep 22, 2026
This was referenced Sep 22, 2026
… names A project lives on the backend and a pipeline lives in one browser, and the resources API will only accept a pipeline that the backend itself holds. So a project records a browser-held pipeline as what it honestly is — a row with no content of its own — and says in extra_data which pipeline it names. Nothing is copied, so there is no second version to drift. The id is tried before the name when resolving one. Renaming a pipeline frees its name, and the next pipeline to take that name is a different pipeline; by name alone that resolves to the wrong one and looks successful. Ids are never reused, so an id that resolves is the pipeline that was added. The name stays as the fallback, because most pipelines have no registry row at all. Answering "does this browser still hold it" deserializes nothing: a list of rows costs one pass over the stored names, not one decoded pipeline per row.
The API has no way to record a pipeline that only exists in a browser, so it is stored as a row with no content of its own. That is the database's business, not the reader's: someone looking for their pipelines should find it under Pipelines, counted with them, whether it lives here or on the backend. Removing one no longer offers to delete it. A row that names a pipeline has never held one, so the icon stays an X and the confirmation says the pipeline stays in this browser — and it keeps saying that even when the pointer has become unusable, since a broken pointer still holds nothing worth destroying.
The pipeline is read where it lives, so the preview shows what the pipeline
says now rather than what it said when it was added, and it opens in the
editor under the name it has now — not the one it had then. Validity is
reported for it exactly as for a pipeline on the backend.
When the pipeline cannot be found, the panel says so and says who added it,
rather than rendering the row's empty payload as content — which the document
preview would otherwise show as the two characters "{}".
Judging a spec no longer fails on one too malformed to have an implementation.
A pipeline read out of browser storage is whatever yaml is there, and
declining to judge it beats breaking the panel it is shown in.
The editor link for a pipeline stored on the backend is gone: the editor opens
pipelines by name out of browser storage, so that link led to an empty canvas
and made the pipeline look lost.
The Pipeline item in the Add menu was disabled because nothing a user could actually make was attachable: the API only takes a pipeline the backend holds, and there is no way to put one there from here yet. It now opens a picker of the pipelines in this browser and records the one that is chosen. The picker says up front that a named pipeline cannot be shared, so nobody discovers that later. A pipeline already in the project is shown but not offered again — matched on what each existing row resolves to as well as the name it was added under, so a renamed pipeline is not added twice. The picker mounts only while it is open, so opening a project no longer reads browser storage for a list nobody asked for.
Removing a row took a delete followed by a re-read of the whole list before anything moved on screen — two round trips of nothing happening, which reads as a click that did not land. The row now goes as soon as it is asked for, and comes back if the request fails. The refresh afterwards was also doing more than it needed. A project's key is a prefix of both its resources and its runs, so invalidating it plainly re-read the resource list a second time and the run feed as well, for a change that cannot have touched either. Removing one item was four requests; it is now two.
The tile reads `resource_counts` straight off the API, which knows a browser-held pipeline only as a document, so the page saying "Pipelines (2)" where the tile said "1 pipeline · 2 documents" put the two in contradiction with no way for a reader to tell which was lying. Group on the entity instead, and let each row say what it is with an icon of its own, since a group headed "Documents" now holds rows that are not all documents. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The editor addresses a pipeline by name out of browser storage, so the link resolved a backend pipeline's name against the wrong store: where a local pipeline happened to share the name it opened that one instead, and where none did it opened an empty canvas. Routing to a backend pipeline does not exist yet, so the preview says so rather than offering a way in that goes somewhere else. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Marking a browser-held pipeline with the same icon a real pipeline carries said the two were the same thing, one line apart in the same table. A group heading names the entity the API files its rows under, so what a row calls itself in `extra_data.kind` is said on the row as a badge, where it reads as a qualifier rather than a claim to be something else. Any kind gets one, not just this one — a `data_sheet` document says so without the table needing to know the word. A row with no kind, or one that only repeats its own entity, stays bare. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
camielvs
force-pushed
the
09-18-feat_local_pipelines_in_projects
branch
from
September 23, 2026 20:57
53a3efb to
826eab1
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
A project lives on the backend. A pipeline lives in one browser's IndexedDB. Until now the Pipeline item in a project's + Add menu was disabled, because the only pipeline the resources API accepts is one the backend already holds — and on this branch there is no way to put one there. So the one thing a user can actually make was the one thing they could not add.
This branch closes that gap as things actually are: a project row points at a pipeline in the browser that made it. Nothing is copied.
The alternative was a snapshot of the YAML, which would be shareable but would drift from the pipeline it was taken from the moment anyone edited it. Drift is the worse failure, so a pointer it is — and the page says plainly what that costs rather than pretending.
The row
The entity enum is closed (
pipeline | agent_session | document) and apipelinemust name a backend pipeline by UUID. A made-up UUID is accepted by the API, but it would sit in a shared table looking exactly like a real backend pipeline id to every other client and to agents, which would then fail to fetch it. So the row is filed as the only thing it honestly is — one carrying no content of its own — andextra_datarecords what it names:The pointer has to live in
extra_data, notpayload: the list endpoint omits payloads entirely, so a list that needed to know what each row held would cost one request per row.Resolution: the id first, then the name
The id is tried before the name, which is the opposite of what seems natural. A pipeline's content is keyed by name, and the name finds every pipeline while the id only exists for those the newer editor has touched — so name-first looks right. It is wrong on one ordinary sequence:
By name, the project now resolves to a different pipeline, and looks perfectly successful doing it. Registry ids are never reused, so an id that still resolves is the pipeline that was added. The name stays as the fallback, and a pointer that resolves by neither reports itself as unresolved rather than guessing.
The id is read, never created. Creating one would enter the pipeline into the registry the newer editor builds its folder listing from, so adding a pipeline to a project would quietly change what another page shows.
What this costs, stated plainly
/projectscards readresource_countsstraight off it. The project page counts the same way rather than contradicting the card, so a browser-held pipeline sits under Documents with aLocal pipelinebadge. Honest, but it does mean the group heading is not the whole truth about its rows.Worth raising separately: a
pipeline_refmember in the entity enum would remove thedocumentfiction entirely, fix?entity=filtering and the card counts, and costs an enum member plus aPAYLOAD_ENTITIESentry.Also here
A document says what kind it is. Any row whose
extra_data.kindnames something its group heading does not gets that kind as a badge beside its name —Local pipelinehere, but adata_sheetdocument would readData sheetwithout the table learning the word. A row with no kind, or one that only repeats its entity, stays bare.Removing one no longer offers to delete it.
removingDestroystreated "carries noentity_id" as "holds the only copy", which would have given these rows a bin and "deleting it here deletes it for good." It now asks the weaker question — does the row claim to name a pipeline — so even a row whose pointer has become unusable is still removed, not deleted.The editor link for a backend pipeline is gone. The editor addresses a pipeline by name out of browser storage, so the link resolved a backend pipeline's name against the wrong store — opening a local namesake where one existed, an empty canvas where none did. Routing to a backend pipeline does not exist yet, so the preview says so instead.
Judging a spec no longer throws on one too malformed to have an
implementation. A pipeline read from browser storage is whatever YAML is there, and declining to judge it beats breaking the panel it is shown in.The picker mounts only while open, so opening a project no longer reads browser storage for a list nobody asked for.
Verification
npm run validateand the full suite (2761 tests) green. Unit tests cover the resolution order — including the recycled-name case above as an explicit regression test — the row encoding, the removal wording, the preview's two states, and the picker.Driven in a real browser against a live backend, in both themes: a pipeline imported through the app's own import path, added through the picker, previewed as its stored YAML with a validity verdict and a working editor link; offered but disabled on reopening the picker; removed with an X while a document keeps its bin; the page's group counts read back and checked against the card's summary for the same project; and a pointer naming a pipeline this browser does not hold rendering as "Not in this browser" with no editor link and no
{}leaking from the empty payload.🤖 Generated with Claude Code