Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6f088a2
✨ Add the terminal provider boundary and pane authority (#730)
taras Sep 2, 2026
259520e
✨ Run a terminal grid's panes concurrently through the provider (#730)
taras Sep 2, 2026
9bfbf18
✨ Run a document's terminal grid panes through the provider (#730)
taras Sep 2, 2026
75bdb27
♻️ Drop an unused parameter from the grid's pane work (#730)
taras Sep 2, 2026
08c275f
✨ Give terminal grids an authority boundary and durable pane children…
taras Sep 2, 2026
14ae876
🐛 Repair durableSpawn, and put the grid on it (#730)
taras Sep 2, 2026
271c627
📝 Decide the cancelled-child contract and TG17's replay boundary (#730)
taras Sep 2, 2026
de092e7
✨ Implement DEC-040, and complete TG15 and TG17 (#730)
taras Sep 2, 2026
871c9bd
🐛 Make the replay evidence deterministic, and pin DEC-040's boundarie…
taras Sep 2, 2026
ce6a37a
♻️ Coordinate the DEC-040 rows by signal, not by duration (#730)
taras Sep 2, 2026
64292a4
🐛 Observe every disposal surface, and make reader close cooperative (…
taras Sep 2, 2026
4cb91ed
📝 Define reader-close cancellation commit boundary (#730)
taras Sep 2, 2026
4f22ff4
✨ Implement the reader-close cancellation commit boundary (#730)
taras Sep 2, 2026
824d2f0
♻️ Defer the grid owner's cancellation at the live close boundary (#730)
taras Sep 2, 2026
5b51988
✅ Count what TG19 proves: resources, records and the lease (#730)
taras Sep 2, 2026
1018291
📝 Make PaneTerminal the pane-work boundary (#730)
taras Sep 11, 2026
4b1bf53
♻️ Make PaneTerminal the only capability pane work receives (#730)
taras Sep 11, 2026
bb5c125
♻️ Give one owner the grids a terminal installation issued (#730)
taras Sep 11, 2026
0828774
♻️ Present a provider's grid as a resource, and make acquisition read…
taras Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 156 additions & 57 deletions architecture.md

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions packages/core/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,29 @@ export { DocumentOutput } from "./src/api.ts";
export type { DocumentOutputApi } from "./src/api.ts";
export { useNormalizedOutput } from "./src/output/normalize.ts";
export { useTerminalOutput } from "./src/output/terminal.ts";
// Only what a provider needs: the refusal it can meet, and the shape of the
// function it is handed. Issuing a grid, opening an installation and converging
// the two are core's own, and a host reaches the whole of it through
// `installTerminalGridProfile`.
export { TerminalGridPresentationError } from "./src/terminal/presentation.ts";
export type { PresentTerminalGrid } from "./src/terminal/presentation.ts";
export {
installTerminalProvider,
registerTerminalProvider,
TERMINAL_PROVIDERS_API,
TerminalProviderInstallError,
TerminalProviders,
} from "./src/terminal/provider-api.ts";
export type {
TerminalProviderFactory,
TerminalProviderInstallRequest,
TerminalProviderOptions,
} from "./src/terminal/provider-api.ts";
export { installTerminalGridProfile } from "./src/terminal/profile.ts";
export type { TerminalGridProfileOptions } from "./src/terminal/profile.ts";
export { paneTerminal } from "./src/terminal/pane.ts";
export type { PaneTerminal } from "./src/terminal/pane.ts";
export type { PaneStatus, RetainedGrid, RetainedPaneOutcome } from "./src/terminal/grid.ts";

export { execute, Execution } from "./src/execute.ts";
export type {
Expand Down
168 changes: 140 additions & 28 deletions packages/core/src/expand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ import {
import type { StructuralViolation, SwitchCase, TerminalPane } from "./structural-rules.ts";
import { terminalGridLayout } from "./terminal-grid.ts";
import type { PlacedPane } from "./terminal-grid.ts";
import { durableGrid, openTerminalGrid, toRequest } from "./terminal/grid.ts";
import type { PaneWork } from "./terminal/grid.ts";
import { recordGridLayout } from "./terminal/journal.ts";
import { usePaneTerminal } from "./terminal/pane.ts";
import {
asBindingViolation,
asExpressionViolation,
Expand Down Expand Up @@ -143,7 +147,7 @@ import {
import { remark } from "remark";
import { select as cssSelect } from "unist-util-select";
import { toString as mdastToString } from "mdast-util-to-string";
import { liveEnvironment } from "./live-env.ts";
import { derivedEnvironment, liveEnvironment } from "./live-env.ts";
import { TestHarnessComponentDefinition } from "./test-harness.ts";
import type { TestHarnessBinding } from "./test-harness.ts";

Expand Down Expand Up @@ -1185,7 +1189,14 @@ function* expandListSegments(
if (segment.name === "Terminal.Grid") {
// No raise() here, like the branches above: expandTerminalGrid
// reports every error it creates.
yield* expandTerminalGrid(segment, result);
yield* expandTerminalGrid(segment, result, {
parentMeta,
parentProps,
hideSet,
path: elementPath,
checkedFailures,
authority,
});
break;
}

Expand Down Expand Up @@ -2106,7 +2117,21 @@ function* resolveStructuralProp(
* does, which is what makes the refusal a closed one rather than a partial grid
* left behind.
*/
function* expandTerminalGrid(segment: ComponentElement, owner: Segment[]): Operation<void> {
/** Everything a pane's own content needs to expand where the grid was written. */
interface GridSite {
readonly parentMeta: Record<string, unknown>;
readonly parentProps: Record<string, Json>;
readonly hideSet: Set<string>;
readonly path: string;
readonly checkedFailures: CheckedFailures | undefined;
readonly authority: ExpansionAuthority | undefined;
}

function* expandTerminalGrid(
segment: ComponentElement,
owner: Segment[],
site: GridSite,
): Operation<void> {
const structure = terminalGridStructure(segment);
if (structure.violations.length > 0) {
for (const violation of structure.violations) {
Expand Down Expand Up @@ -2141,23 +2166,119 @@ function* expandTerminalGrid(segment: ComponentElement, owner: Segment[]): Opera
}

const layout = terminalGridLayout(columns.value, placed);
owner.push(
yield* raise({
type: "error",
message: positioned(noTerminalProviderMessage(), segment),
source: "Terminal.Grid",
// The grid the author asked for, carried beside the sentence so an
// assertion is about the layout that was derived rather than about the
// wording of a refusal.
cause: {
layout: {
columns: layout.columns,
rows: layout.rows,
cells: layout.cells.map((cell) => ({ ...cell })),
},
// The grid renders nothing into the document: what a pane shows belongs to
// that pane, and the sibling after `</Terminal.Grid>` renders to the root
// again only once the provider has restored it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// again only once the provider has restored it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// again only once the provider has restored it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// again only once the provider has restored it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// again only once the provider has restored it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// again only once the provider has restored it.

const identity = {
path: site.path,
...(segment.position === undefined ? {} : { position: segment.position }),
};

try {
// Recorded in this coroutine, before the lease and before any provider is
// contacted: a resumed run whose grid changed is refused while nothing has
// been opened. It cannot live inside the grid child, because a completed
// child never runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// child never runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// child never runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// child never runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// child never runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// child never runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// child never runs.

yield* recordGridLayout(identity, toRequest(layout));

const retained = yield* durableGrid(function* (boundary) {
const work = structure.panes.map((pane, index) =>
paneWork(pane, layout.cells[index]!.title, site),
);
return yield* openTerminalGrid(layout, work, boundary);
});

const failed = retained.panes.find((pane) => pane.status === "failed");
if (failed !== undefined) {
owner.push(yield* raise(terminalGridError(segment, failed.reason)));
}
} catch (error) {
owner.push(
yield* raise(
terminalGridError(segment, error instanceof Error ? error.message : String(error)),
),
);
}
}

/**
* What one authored pane does once the grid has created its terminal.
*
* A self-closing pane runs the host's default shell as a terminal activity,
* exactly as a paired pane's content does. A paired
* pane expands its own content in a scope of its own: it inherits the bindings,
* providers, configuration and working directory visible where the grid was
* written, and everything it creates afterwards stays inside the pane. Its
* `<Break>` cannot reach a loop outside the grid, its `<Return>` cannot claim an
* enclosing body, and a checked failure settles the pane rather than poisoning
* the root or a sibling.
*/
function paneWork(pane: TerminalPane, title: string, site: GridSite): PaneWork {
if (pane.form === "self-closing") {
return {
ordinal: pane.ordinal,
*run(terminal, grid) {
// The shell is this pane's one terminal activity, and acquiring it is
// what makes the pane ready — the same boundary a paired pane's content
// crosses, rather than a second way in.
const outcome = yield* terminal.use(grid.shell(pane.ordinal));
if (outcome.signal !== undefined) {
throw new Error(`pane ${pane.ordinal} ("${title}") shell ended on ${outcome.signal}`);
}
if (outcome.exitCode !== undefined && outcome.exitCode !== 0) {
throw new Error(
`pane ${pane.ordinal} ("${title}") shell exited with status ${outcome.exitCode}`,
);
}
},
}),
);
};
}

return {
ordinal: pane.ordinal,
*run(terminal, grid) {
yield* scoped(function* () {
// A pane is not inside the loop the grid was written in, so a <Break>
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// in its content has no loop to exit and says so.

yield* ActiveLoop.set(undefined);
yield* usePaneTerminal(terminal);
const siteEnv = yield* env;
// Starts from what the grid site can see and keeps its own writes: a
// binding this pane makes is visible to later work in this pane and to
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// nothing else.

yield* provideEnv(derivedEnvironment(siteEnv, { ...(siteEnv?.values ?? {}) }));

const shown: Segment[] = [];
yield* expandSegmentsWithin(
pane.element.children,
site.parentMeta,
site.parentProps,
site.hideSet,
// A counter of its own. Panes expand concurrently, and a shared
// mutable counter would hand two of them block identities that depend
// on which happened to run first.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// on which happened to run first.

createBlockCounter(),
shown,
extendPath(
site.path,
elementFrame(pane.element.name, elementSite(pane.element.position, pane.index)),
),
0,
// The pane's own ledger: a checked failure settles this pane and
// cannot reach the root or a sibling.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// cannot reach the root or a sibling.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// cannot reach the root or a sibling.

containedLedger(site.checkedFailures),
site.authority,
// No enclosing value body: a <Return> written in a pane cannot claim
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant comment — restates what the code does.

Suggested change
// one outside the grid.

undefined,
);
const text = renderSegments(shown);
if (text.length > 0) {
yield* grid.display(pane.ordinal, text);
}
});
},
};
}

/** The label one pane displays, from the value its own `title` prop produced. */
Expand All @@ -2172,15 +2293,6 @@ function* resolvePaneTitle(pane: TerminalPane): Operation<Result<string>> {
return terminalTitle(value.value);
}

/** What a complete grid says on a host where nothing can open one. */
function noTerminalProviderMessage(): string {
return (
"no terminal provider opened this grid. A host installs the terminal-grid capability " +
"explicitly, and this one installs none, so no pane expanded its content and no default " +
"shell started."
);
}

function loopError(segment: ComponentElement, message: string): ErrorSegment {
return { type: "error", message: positioned(message, segment), source: "Loop" };
}
Expand Down
Loading
Loading