Skip to content

feat: project page - #2738

Draft
camielvs wants to merge 18 commits into
09-17-explore_new_project_cardfrom
09-17-feat_project_page
Draft

camielvs wants to merge 18 commits into
09-17-explore_new_project_cardfrom
09-17-feat_project_page

Conversation

@camielvs

Copy link
Copy Markdown
Collaborator

Stage 2 of the projects MVP: /projects/$projectId becomes a real page, replacing the placeholder from #2734. Branches off #2735.

Layout

Three columns, stacking to one below xl:

Column Holds
Left, 27rem Resources as a grouped table, then the project's runs
Middle, fills A preview of the selected resource
Right, 20rem Description, notes, metadata, and every project action

All three columns share a heading row of the same height, so their panels start on one line rather than the sidebar riding up above the preview.

Resources

Rows grouped under an inline heading per kind, which counts its group. A kind the project holds none of is left out rather than shown empty. Clicking a row previews it; clicking it again returns to nothing selected. Remove is inline, labelled after the item it removes, behind a confirmation that is explicit that it takes the item out of the project without deleting what the item points at.

Adding is one Add menu. Only documents can be added here: a pipeline resource needs a stable server-side pipeline id, which local pipelines do not have yet. Pipeline and agent session appear disabled rather than hidden, so the gap reads as a roadmap.

Preview

  • Document → its own body, highlighted by what its name implies (readme.md → markdown, no extension → plain text).
  • Pipeline → the yaml of its saved spec, plus a link into the editor built with the same getDefaultEditorPath the pipelines dashboard uses, so it follows the v2_editor flag.
  • A payload with no body of its own → dumped as yaml rather than shown blank. This is also the path an entity kind the frontend does not know yet will take.
  • Nothing to show, or a pipeline that is gone → says so, rather than an empty viewer.

Runs

Each run carries its overall status, from the same task counts the main runs table reads. Who started a run is left out: a run is reached through the project that holds it.

Two endpoints written out by hand

GET /api/projects/{id}/runs and GET /api/pipelines/{id} are both absent from the checked-in generated client. Regenerating emits every operation of whatever backend happens to be running — the checked-in copy has 61 functions against 124 live operations — so that is worth doing deliberately on its own branch rather than inside a UI change. Both calls go through the already-configured client, so they inherit the base url and headers like any generated call, and each carries a comment saying why it is hand-written.

Things a reviewer should know

  • Execution stats have to be asked for. GET /api/pipeline_runs/{id} omits execution_status_stats unless include_execution_stats is passed, and the shared fetchPipelineRun does not pass it. A first cut of the status column silently rendered every run as unknown because of this. This branch asks for its own rather than changing the payload every other caller of that helper caches — but a include_execution_stats on the project runs endpoint would make the whole column free instead of one request per row.
  • A run cannot currently be put into a project from the client. Run annotations do not drive the feed and neither create-run endpoint takes a project id, so the section stays empty until that wiring exists. The render path was verified by stubbing only the feed and letting each row's status call hit a real backend.
  • The editor link resolves a pipeline by name against local storage. A project resource points at a backend pipeline id, so a pipeline the user has never had locally opens an empty editor. That is the area being rewritten separately; no workaround invented here.
  • Unknown entity kinds sort after known ones and pluralise correctly, but creation is restricted to pipeline | agent_session | document, so that path is covered by unit tests only.

Verification

npm run validate clean, full suite passing. Driven end to end against a local backend at 2000 / 1440 / 1200 / 900 px in both themes: no horizontal overflow on either table, description and notes persist across a reload, add and remove a document, a real pipeline renders as yaml and its editor link loads, a deleted pipeline says so, removing the previewed item clears the preview. Seeded and cleaned up through the API in a try/finally so a mid-run failure could not leave rows behind.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: 09-17-feat_project_page/8f3a12c

@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from 15b2a3f to b07c324 Compare September 18, 2026 21:19
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch from d70b987 to 1e2f163 Compare September 18, 2026 21:19
@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from b07c324 to ff8d4e6 Compare September 18, 2026 21:26
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch from 1e2f163 to 501de46 Compare September 18, 2026 21:26
@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from ff8d4e6 to a4b9b99 Compare September 18, 2026 21:44
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch from 501de46 to 98b2313 Compare September 18, 2026 21:45
@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from a4b9b99 to d3bc8fa Compare September 18, 2026 22:09
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch from 98b2313 to 74d9257 Compare September 18, 2026 22:09

camielvs commented Sep 18, 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-17-explore_new_project_card branch from d3bc8fa to c23da09 Compare September 23, 2026 20:57
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch from 74d9257 to 1b73d44 Compare September 23, 2026 20:57
@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from c23da09 to e2ed9cc Compare September 23, 2026 23:00
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch 2 times, most recently from 1a0f4ce to 4fe29b1 Compare September 23, 2026 23:36
@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from e2ed9cc to 2379fd2 Compare September 23, 2026 23:36
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch from 4fe29b1 to ff3a142 Compare September 23, 2026 23:57
@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from 2379fd2 to 40c40a8 Compare September 23, 2026 23:57
camielvs and others added 18 commits September 23, 2026 17:07
The route landed on a placeholder. It now shows the project it names: who
made it, when, and the actions that were previously only reachable from its
card. A deleted project returns to the list rather than leaving the visitor
on a page that no longer exists.

A missing project reads as missing rather than as a failure, since a shared
project URL outlives the project.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Description and notes are editable on the page and save when the field is
left, the way run notes already work. Notes had no home before this: they
come back only from the single-project response, so no other surface could
show them.

Renaming goes through a dialog rather than an in-place field, since a name
wrong by a keystroke is worse than a description wrong by one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resource counts on a card said a project held three pipelines without saying
which. The page lists them, grouped by kind, and every kind is named even
when empty so the shape of a project is legible before anything is in it.

An unrecognised kind still gets a group: the backend stores the entity as a
plain string and expects more members, so an unknown one must not vanish.

Removing an item says plainly that it leaves the pipeline or document itself
alone, since "remove" next to a project that hard-deletes its contents is
otherwise an alarming word.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documents are the one kind of resource a project can hold without pointing
at something else, so they are the one kind this page can create: the
backend requires a document to carry a payload and forbids it an entity_id,
both confirmed against the API before the form was shaped.

The payload keeps the body under `content`. Nothing server-side validates
inside the payload, so that key is this app's convention and not a rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The page description promises pipelines and runs, so the runs belong on it.
The endpoint exists but is absent from the generated client, whose checked-in
copy predates it; regenerating would swap the whole client for whatever
backend happens to be running, so this one call is written out by hand
against the client the provider already configures.

A 4xx is no longer retried. Without that, a url for a deleted project sat on
a spinner through three backoffs before it could say the project was gone,
which made the not-found state effectively unreachable.

Runs carry no execution status, so none is shown rather than implied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ource cards

The stacked page spent a full-width row on two textareas and then split the
resources into three near-empty tables, so a project with four items filled a
screen and a half. Viewing and editing now sit side by side: the main column
holds the resources and the runs, and a right-hand column holds the
description, notes, metadata and every project action.

Resources are one grid of cards of whatever kind, with a type filter that
appears only once there is more than one kind to filter, so a project of
documents never shows a filter with one option. The inline "Add document"
button becomes a single "Add" menu; pipelines and agent sessions sit in it
disabled under a line saying where they will come from, which reads as a
roadmap rather than a missing feature.

The header keeps only the back link and the project's name, and the actions it
used to hide behind a kebab are three labelled buttons in the sidebar.
…lumn

At 2000px the capped grid left a third of the window empty and the sidebar
sat in the middle of it. The sidebar is now a fixed 320px rail against the
right edge and the resources and runs sit in a centred column of their own,
capped so a five-card row does not stretch across the whole monitor. The
header moved inside that column so the page reads as one document beside a
rail rather than a title adrift at the far left.

The empty state and the add menu drop their "coming soon" wording: the
disabled entries say enough on their own.
Centring it left a wide gap between the page's left edge and its first
heading, so the content read as adrift rather than composed. The column now
starts at the gutter and keeps a cap, and the sidebar stays against the right
edge; the empty state is held to a narrower box so it does not float to the
middle of an otherwise left-aligned column.
The page had a wide empty middle and no way to look inside anything it
listed. Picking a card now previews it beside the list: a document shows its
own body, highlighted by whatever its name implies, and a pipeline shows the
yaml of its saved spec. An item whose payload has no body of its own is
dumped as yaml rather than shown as blank, and an item carrying nothing at
all says so.

`GET /api/pipelines/{id}` is missing from the checked-in generated client, so
it is written out by hand against the configured client for the same reason
the project runs call is: regenerating would replace every operation from
whatever backend happens to be running.

Runs joins the resources in the first column, which widens to hold both. All
three columns now carry a heading of the same height, so their panels start
on one line instead of the sidebar riding up above the preview.
Cards cost the width the preview needs: at four or five items they wrapped
into a block as tall as the viewer beside them, and squaring the two columns
up would have taken a third of the preview away. Rows under an inline heading
per kind say the same thing in a quarter of the space, and the heading counts
its group, so the filter pills are gone with nothing lost.

A kind the project holds none of is left out rather than shown empty: the add
menu already says what a project can hold, and three empty groups in a narrow
column is the noise the cards were.
A row was clickable but said nothing about it: the default cursor over the
name read as text, and the only action behind the kebab was a remove, so the
menu was two clicks wrapping one. The row now shows a pointer and carries the
remove inline, named after the item so it reads on its own.

Nothing could get back out of a preview either. Picking the row that is
already showing puts the column back to nothing selected, and a Clear beside
the preview heading says so for anyone who would not think to try.
Picking the showing row again already puts the column back to nothing
selected, so a second control for it was only another thing to keep in step.
The runs table said what ran and when but not how it went, and it was set in
a type a size larger than the resources beside it. Rows now match that
typography and carry the run's overall status, icon and label, from the same
task counts the main runs table reads.

Those counts turn out to need asking for: `/api/pipeline_runs/{id}` omits
`execution_status_stats` unless `include_execution_stats` is passed, and the
shared `fetchPipelineRun` does not pass it, so a first cut of this silently
rendered every run as unknown. The project page asks for its own rather than
changing the payload every other caller of that helper caches.

The author column is gone: a run is reached through the project that holds it,
so who started it earns less room than what it did.
… runs table scrolling

The started column was 96px holding a 104px date, and the table cell it sits
in cannot wrap, so the runs table scrolled sideways by exactly that overflow.
The column is wide enough now, the resources table gets the same treatment
before it grows the same problem, and the whole column is wider, so a run's
pipeline name has 208px instead of 176.

A previewed pipeline also gets a link into the editor, built by the same
`getDefaultEditorPath` the pipelines dashboard uses, so it follows the
`v2_editor` flag. The editor addresses a pipeline by the path it is saved
under, which is not always its title, so the fetch now returns that alongside
the spec.
…ires

Rebasing onto the current service branch picked up `extraData` as a required
field on a project and its resources, so every fixture on this branch has to
name it.
…eting it

A bin icon that turned red on hover read as "destroy this pipeline", which is
the one thing the action does not do. An X in the row's own colour says
"take it out of here", and the confirmation now says plainly that the item is
not deleted and stays wherever it lives.
…n apart

The table had no header row, so `table-fixed` had nothing to size its
columns from and split them evenly, leaving the row action stranded in
the middle of a wide cell. It now carries the same header row the runs
table beside it does, which is where the widths belong.

An item the project only points at is unlinked when removed, but a
document lives nowhere else and is destroyed. The row now says which:
an X and "remove from this project" for a reference, a bin and
"delete" for something the project holds outright, with the
confirmation wording to match.
A verdict only, green or red: anyone who wants the detail opens the
pipeline. It reuses the editor's own validator so the two never
disagree, and runs on the spec the preview has already fetched, so it
costs no extra request and no perceptible wait.

A pipeline whose tasks arrived without their component specs gets no
verdict at all rather than a misleading red, since the validator reads
embedded specs and never fetches one.
@camielvs
camielvs force-pushed the 09-17-explore_new_project_card branch from 40c40a8 to 89b0612 Compare September 24, 2026 00:13
@camielvs
camielvs force-pushed the 09-17-feat_project_page branch from ff3a142 to 8f3a12c Compare September 24, 2026 00:13

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