Every surface in Studio is laid out at design time and is identical for everyone who opens it. A domain modeller who checks the same three things every morning, a reviewer who only ever wants the queue, and someone joining a workspace for the first time all get the same Build, the same Graph, the same Ask. The only way to see something the layout did not anticipate is to ask for it again, read it, and throw it away.
The block protocol already removed the reason for that. A skill emits blocks and a surface decides what to do with them, so a rendered answer is no longer a paragraph of prose that only makes sense where it was produced. What is missing is somewhere to keep one.
What this is
A workspace member can pin a block from an answer onto a board of their own, and that board is what they land on.
- Ask a question. The answer renders as blocks, the way it does now.
- Pin any block. Name a board or add it to one that already exists.
- The board is per member. Two people in the same workspace see different boards, because they do different jobs.
The rule that makes it worth building
A board stores the query, never the values.
A pinned widget carries the question that produced it, not the figures the model reported when it was pinned. Every load recomputes it against the current graph. That is the difference between a board that stays true and a screenshot that rots in a week.
It also puts the determinism boundary in the right place:
- The model decides what is worth asking and how it should be drawn. That is judgement, and it is what a model is for.
- The values come from a query executor on the server. They are never text the model typed. A chart of node counts per bounded context is the server counting, not the model estimating.
A render call therefore takes a query and a presentation, and refuses to accept precomputed numbers. The frontend runs the query and draws the result.
What it needs
- A saved query specification in
@braidhq/schema: what to select from the graph, how to group it, how to order it. Serialisable, versioned, and executable without a model in the loop.
- A query executor in
core that runs one against the graph, plus a read route that returns its result.
- Presentation blocks the current union does not have: headline figures and a chart. Both take a query rather than data points.
- A board repository, keyed by workspace and member, holding an ordered set of pinned widgets and their layout.
- A pin affordance on rendered blocks, and a board surface that recomputes every widget on load.
Prior art worth copying exactly
An internal demo (llm-application-demo, the DottedSign Portfolio page) already shipped this shape and the two decisions above are the ones that made it work. Its render tools are documented to the model as "pass the query to compute, never the figures themselves, the frontend runs it", and its boards persist widgets as query specifications so a reload recomputes rather than replays. Its deterministic layer resolves values through JSONata references, which is the same separation this issue is asking for: a declarative expression the server evaluates, with the model choosing which expression, never what it returns.
Worth reading before designing the schema, because the failure modes it already went through (a pinned widget going stale, a model quietly rounding a number it was asked to repeat) are the ones this will hit.
Roles come later
Per member is the unit to build first. A role-shaped default board, where joining a workspace as a reviewer gets you the reviewer's layout, is the same machinery with a different owner on the record. Not in this issue, but the repository key should not make it impossible.
Not doing
- Editing a pinned widget's query by hand. Repin it by asking again.
- Sharing a board with another member. Ownership first, sharing once there is something worth sharing.
- Boards for anything but the graph. Run history and source state are not query targets yet.
Where the board lives
Its own surface, not a section of the one that produced the pinned block.
The content comes from anywhere blocks are rendered. Today that is Ask, but coverage on Build and a slice on Graph are blocks too, and pinning one of those "into Ask" is not a sentence that means anything. A board is a destination, not a feature of a producer.
That splits cleanly:
- Pinning is an affordance on a rendered block, wherever it is rendered. Every surface that draws blocks gets it for free.
- The board is a surface of its own, reached from the rail, holding whatever its owner put there.
The two alternatives both break:
- Inside Build. Build's subject is how much of the corpus has been read. The board's subject is what the model now says. Two unrelated things on one page makes both vague.
- Inside Ask. Ask is a conversation. A board is a durable object that must be openable without entering a conversation, which is a separate entry point, which is a separate surface.
Once it exists it is the right landing surface: per member, recomputed on load, and the only page that answers "where do things stand for me" rather than "here is a subsystem". The rail is already ordered so it can take the top slot without disturbing anything below it.
Every surface in Studio is laid out at design time and is identical for everyone who opens it. A domain modeller who checks the same three things every morning, a reviewer who only ever wants the queue, and someone joining a workspace for the first time all get the same Build, the same Graph, the same Ask. The only way to see something the layout did not anticipate is to ask for it again, read it, and throw it away.
The block protocol already removed the reason for that. A skill emits blocks and a surface decides what to do with them, so a rendered answer is no longer a paragraph of prose that only makes sense where it was produced. What is missing is somewhere to keep one.
What this is
A workspace member can pin a block from an answer onto a board of their own, and that board is what they land on.
The rule that makes it worth building
A board stores the query, never the values.
A pinned widget carries the question that produced it, not the figures the model reported when it was pinned. Every load recomputes it against the current graph. That is the difference between a board that stays true and a screenshot that rots in a week.
It also puts the determinism boundary in the right place:
A render call therefore takes a query and a presentation, and refuses to accept precomputed numbers. The frontend runs the query and draws the result.
What it needs
@braidhq/schema: what to select from the graph, how to group it, how to order it. Serialisable, versioned, and executable without a model in the loop.corethat runs one against the graph, plus a read route that returns its result.Prior art worth copying exactly
An internal demo (
llm-application-demo, the DottedSign Portfolio page) already shipped this shape and the two decisions above are the ones that made it work. Its render tools are documented to the model as "pass the query to compute, never the figures themselves, the frontend runs it", and its boards persist widgets as query specifications so a reload recomputes rather than replays. Its deterministic layer resolves values through JSONata references, which is the same separation this issue is asking for: a declarative expression the server evaluates, with the model choosing which expression, never what it returns.Worth reading before designing the schema, because the failure modes it already went through (a pinned widget going stale, a model quietly rounding a number it was asked to repeat) are the ones this will hit.
Roles come later
Per member is the unit to build first. A role-shaped default board, where joining a workspace as a reviewer gets you the reviewer's layout, is the same machinery with a different owner on the record. Not in this issue, but the repository key should not make it impossible.
Not doing
Where the board lives
Its own surface, not a section of the one that produced the pinned block.
The content comes from anywhere blocks are rendered. Today that is Ask, but coverage on Build and a slice on Graph are blocks too, and pinning one of those "into Ask" is not a sentence that means anything. A board is a destination, not a feature of a producer.
That splits cleanly:
The two alternatives both break:
Once it exists it is the right landing surface: per member, recomputed on load, and the only page that answers "where do things stand for me" rather than "here is a subsystem". The rail is already ordered so it can take the top slot without disturbing anything below it.