@hachej/boring-ui-kit 0.1.111 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +255 -239
- package/THIRD_PARTY_NOTICES.md +85 -0
- package/dist/anchor-conformance.d.ts +78 -0
- package/dist/anchor-conformance.js +288 -0
- package/dist/canvas-document.d.ts +25 -0
- package/dist/canvas-document.js +141 -0
- package/dist/canvas-editor-native.d.ts +22 -0
- package/dist/canvas-editor-native.js +112 -0
- package/dist/canvas-editor.d.ts +6 -0
- package/dist/canvas-editor.js +13 -0
- package/dist/canvas-mounted.d.ts +41 -0
- package/dist/canvas-mounted.js +180 -0
- package/dist/canvas-proposals.d.ts +11 -0
- package/dist/canvas-proposals.js +45 -0
- package/dist/canvas-records.d.ts +3 -0
- package/dist/canvas-records.js +39 -0
- package/dist/canvas.d.ts +92 -0
- package/dist/canvas.js +264 -0
- package/dist/contracts.d.ts +125 -0
- package/dist/contracts.js +1 -0
- package/dist/experience-catalog.d.ts +142 -0
- package/dist/experience-catalog.js +25 -0
- package/dist/experience-compose.d.ts +83 -0
- package/dist/experience-compose.js +218 -0
- package/dist/experience-composition-schema.d.ts +29 -0
- package/dist/experience-composition-schema.js +22 -0
- package/dist/experience-document-viewer.d.ts +13 -0
- package/dist/experience-document-viewer.js +54 -0
- package/dist/experience-document.d.ts +64 -0
- package/dist/experience-document.js +234 -0
- package/dist/experience-region-text.d.ts +3 -0
- package/dist/experience-region-text.js +84 -0
- package/dist/experience-region-tree.d.ts +22 -0
- package/dist/experience-region-tree.js +50 -0
- package/dist/experience-regions.d.ts +9 -0
- package/dist/experience-regions.js +122 -0
- package/dist/experience-renderer.d.ts +16 -0
- package/dist/experience-renderer.js +37 -0
- package/dist/experience.d.ts +12 -0
- package/dist/experience.js +23 -0
- package/dist/file-tree-shadcn.d.ts +8 -0
- package/dist/file-tree-shadcn.js +14 -0
- package/dist/file-tree-view.d.ts +66 -0
- package/dist/file-tree-view.js +256 -0
- package/dist/file-tree.d.ts +57 -0
- package/dist/file-tree.js +204 -0
- package/dist/html-preview.d.ts +2 -0
- package/dist/html-preview.js +47 -0
- package/dist/html-viewer.d.ts +13 -0
- package/dist/html-viewer.js +69 -0
- package/dist/html.d.ts +21 -0
- package/dist/html.js +21 -0
- package/dist/index.d.ts +2 -472
- package/dist/index.js +1 -2671
- package/dist/markdown-editor.d.ts +28 -0
- package/dist/markdown-editor.js +230 -0
- package/dist/markdown-extensions.d.ts +16 -0
- package/dist/markdown-extensions.js +96 -0
- package/dist/markdown-icons.d.ts +244 -0
- package/dist/markdown-icons.js +32 -0
- package/dist/markdown-mounted.d.ts +63 -0
- package/dist/markdown-mounted.js +263 -0
- package/dist/markdown-parser.d.ts +4 -0
- package/dist/markdown-parser.js +33 -0
- package/dist/markdown-proposal-diff.d.ts +5 -0
- package/dist/markdown-proposal-diff.js +122 -0
- package/dist/markdown-safety.d.ts +21 -0
- package/dist/markdown-safety.js +135 -0
- package/dist/markdown.d.ts +86 -0
- package/dist/markdown.js +121 -0
- package/dist/native-chat.d.ts +150 -0
- package/dist/native-chat.js +376 -0
- package/dist/pi.d.ts +12 -0
- package/dist/pi.js +1 -0
- package/dist/public-text-buffer.d.ts +5 -0
- package/dist/public-text-buffer.js +1 -0
- package/dist/remote-chat.d.ts +76 -0
- package/dist/remote-chat.js +324 -0
- package/dist/resources.d.ts +34 -0
- package/dist/resources.js +1 -0
- package/dist/text-buffer-state.d.ts +24 -0
- package/dist/text-buffer-state.js +1 -0
- package/dist/text-buffer.d.ts +52 -0
- package/dist/text-buffer.js +315 -0
- package/dist/text-draft-controls.d.ts +9 -0
- package/dist/text-draft-controls.js +33 -0
- package/dist/text-draft-types.d.ts +111 -0
- package/dist/text-draft-types.js +1 -0
- package/dist/text-drafts.d.ts +29 -0
- package/dist/text-drafts.js +378 -0
- package/package.json +226 -58
- package/styles/file-tree.css +46 -0
- package/dist/styles.css +0 -2308
- package/dist/tokens.css +0 -155
package/README.md
CHANGED
|
@@ -1,308 +1,324 @@
|
|
|
1
1
|
# @hachej/boring-ui-kit
|
|
2
2
|
|
|
3
|
-
<
|
|
3
|
+
The root is pure headless ViewerFeature/ViewerController state/actions/tools, typed descriptors and presentation commands. A ViewerRenderer is composed separately. ViewerFeature<Descriptor, Controller> preserves the complete concrete controller, including flush/custom methods. Concrete controllers use cached immutable snapshots and explicit owner teardown; contract-only features require their own implementation. Inline/record viewers need no dummy file client; a document feature closes over the exact resource capabilities supplied by its host.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](https://www.npmjs.com/package/@hachej/boring-ui-kit)
|
|
5
|
+
`@hachej/boring-ui-kit/resources` adds versioned-document and new/existing-buffer save contracts; install its optional `@hachej/boring-files` peer when used. `@hachej/boring-ui-kit/pi` adds ChatSource returning Pi's exact ConversationWatch (including asynchronous operations/backpressure and close reason); install the optional native Pi peer when used. Neither subpath is imported by the root, and @hachej/boring-agent is not a dependency.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
`createMarkdownController` from `@hachej/boring-ui-kit/markdown` implements document editing without React or Pi. Supply an authenticated ResourceClient, its expected scope/principal/initiator, a saved snapshot or new-document target, and a stable viewer instance and page epoch. Expected identity is comparison metadata and grants no access. The host must bind reads to that identity. Opening the controller performs no reads or writes.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
Call `actions.edit(text)`, then capture `actions.selection()` and pass it to `flush(selection)`. Flush returns saved only for a matching operation digest, exact resource transition and expected receipt identity. Later typing remains dirty. Refresh preserves dirty buffers; `discardToRemote` deliberately discards only edits that existed when it began. Disposing releases listeners and does not close the borrowed client or cancel committed work.
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
Lost or malformed acknowledgements stay unknown. `actions.reconcile()` uses retained operation lookup and never blindly retries after not-found. Another save waits for acknowledgement or reconciliation. Invalid Unicode is refused before publication. A locally installed Markdown source recipe is available; hosted registry qualification remains pending. Native chat is available through the optional entries described below.
|
|
12
|
+
|
|
13
|
+
Historical scaffold tests install/typecheck packed headless UI and UI+files in clean consumers without Pi or an agent package. Candidate runtime installation qualification is recorded separately in the [implementation checkpoint](../../docs/implementation/PARTIAL.md). Import interfaces with `import type`.
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
See [scaffold guide](../../docs/contracts/SCAFFOLD.md) and [audit](../../docs/contracts/ABSTRACTION-REVIEW.md).
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## Exact proposals and commands
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
`actions.propose(selection, edits, summary)` retains an immutable proposal with its exact resource/scope/view/revision, viewer instance/epoch, buffer version, before/after text and ordered edits. It performs no publication and preserves existing human text. Every nonempty `find` must occur once; a missing or ambiguous edit rejects the entire transform.
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
Human `actions.accept(id)` requires the original base still to match, adopts the exact proposed text and flushes that selected buffer through the normal publication path. A changed version remains stale even when the text was edited back. Subscriber edits during adoption prevent that flush; typing during publication remains dirty after acknowledgement. Unknown or pending saves block acceptance until reconciliation. The adopted proposal remains inspectable; `actions.reject(id)` removes only that local proposal. Proposals are ephemeral and do not establish product approval or durable decision state.
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
The concrete `tools.inspect` and `tools.propose` presentation commands require the full live target and an `expiresAt` timestamp. Inspection reports text, dirty state and save selection. Expired, retargeted or disposed commands refuse. No agent acceptance command is exposed. Native tool registration remains host-owned; `@hachej/boring-agent/documents` independently patches saved Markdown without importing a viewer. `readOnly: true` refuses local edits, proposals, acceptance and flush; provider authorization still controls actual writes.
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|---------|--------------|
|
|
28
|
-
| **Pane primitives** | `Pane`, `PaneHeader`, `PaneBody`, `PaneToolbar`, `FloatingPanel` — built for Dockview panels |
|
|
29
|
-
| **~40 components** | Buttons, inputs, dialogs, tooltips, badges, spinners, empty states, settings panels, and more |
|
|
30
|
-
| **Zero global CSS** | Styles use CSS custom properties from the host; no conflicting style sheets |
|
|
31
|
-
| **shadcn-style** | Composable, headless-compatible, restylable via `className` / `class-variance-authority` |
|
|
32
|
-
| **TypeScript-first** | Every component is typed; props are explicit; no implicit `any` |
|
|
33
|
-
| **Works anywhere** | Plugin panels, standalone apps, component fixtures — no framework lock-in |
|
|
25
|
+
Behavior follows the pinned v3 Markdown ledger in [LEGACY-UI.md](../../docs/compatibility/LEGACY-UI.md), with exact base checks and v4 transactional saves. Mounted selection and heading commands are available through the optional renderer below. The optional renderer presents proposal line diffs with folded context.
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
## Optional Markdown editor
|
|
36
28
|
|
|
37
|
-
|
|
29
|
+
Import `MarkdownEditor` from `@hachej/boring-ui-kit/markdown-editor` and pass an existing `MarkdownController`:
|
|
38
30
|
|
|
39
31
|
```tsx
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
EmptyState, Spinner,
|
|
44
|
-
Input, Field, FieldLabel
|
|
45
|
-
} from "@hachej/boring-ui-kit"
|
|
46
|
-
|
|
47
|
-
export function SearchPane() {
|
|
48
|
-
return (
|
|
49
|
-
<Pane>
|
|
50
|
-
<PaneHeader>Search</PaneHeader>
|
|
51
|
-
<PaneBody>
|
|
52
|
-
<Field>
|
|
53
|
-
<FieldLabel>Query</FieldLabel>
|
|
54
|
-
<Input placeholder="Search files…" />
|
|
55
|
-
</Field>
|
|
56
|
-
<EmptyState
|
|
57
|
-
title="No results"
|
|
58
|
-
description="Try a different search term"
|
|
59
|
-
icon="search"
|
|
60
|
-
/>
|
|
61
|
-
<ButtonGroup>
|
|
62
|
-
<Button variant="secondary">Cancel</Button>
|
|
63
|
-
<Button>Search</Button>
|
|
64
|
-
</ButtonGroup>
|
|
65
|
-
</PaneBody>
|
|
66
|
-
</Pane>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
32
|
+
import { MarkdownEditor } from '@hachej/boring-ui-kit/markdown-editor';
|
|
33
|
+
|
|
34
|
+
<MarkdownEditor controller={controller} title="Notes" initialMode="rich" />;
|
|
69
35
|
```
|
|
70
36
|
|
|
71
|
-
|
|
37
|
+
Install the pinned React, React DOM, Marked and Tiptap peers listed in this package's manifest when selecting this entry. They remain optional for headless consumers. The renderer borrows its controller; unmounting does not dispose it or close its resource client. The host owns selection, identity and teardown.
|
|
72
38
|
|
|
73
|
-
|
|
39
|
+
Rich formatting and Markdown source controls share controller state. Mounting, switching modes and projecting remote text never serialize the original buffer. Genuine rich edits use Tiptap's Markdown serialization and may normalize supported formatting. Save and Ctrl/Cmd+S capture the exact controller selection. Proposal review shows added/removed lines with two lines of context and expandable unchanged runs. It preserves exact line endings and marks a missing final newline. An inner details panel retains exact before/after text. Accept and save delegates to the controller; reviewing or expanding context never publishes. Conflict and unknown outcomes expose explicit discard or reconciliation controls.
|
|
74
40
|
|
|
75
|
-
|
|
76
|
-
# pnpm
|
|
77
|
-
pnpm add @hachej/boring-ui-kit
|
|
41
|
+
Detailed alignment uses a matrix of at most 250,000 cells after trimming equal prefix/suffix lines. Larger comparisons show all removed/added middle lines with an explicit notice. This bounds the comparison matrix, not total document size, string-comparison cost or rendered rows.
|
|
78
42
|
|
|
79
|
-
|
|
80
|
-
npm install @hachej/boring-ui-kit
|
|
43
|
+
The rich view includes a Document outline built from the native parsed headings. Navigation changes the native selection without editing or saving Markdown. Rich/source switches retain each mode's selection and direction while the buffer version is unchanged. Text projected from outside (load, refresh, discard, source edits) puts the rich selection at the document start, where the browser leaves its caret, so a click or formatting command never acts on a stale selection at the end. External replacement or source edits invalidate older bookmarks; changing controllers starts a new local session. Human heading controls use the mounted presentation command described below.
|
|
81
44
|
|
|
82
|
-
|
|
83
|
-
git clone https://github.com/hachej/boring-ui.git
|
|
84
|
-
cd boring-ui && pnpm install
|
|
85
|
-
pnpm --filter @hachej/boring-ui-kit build
|
|
86
|
-
```
|
|
45
|
+
### Mounted Markdown commands
|
|
87
46
|
|
|
88
|
-
|
|
47
|
+
Pass `onMountedTools` to receive a concrete `MarkdownMountedTools` handle, or `null` when detached. Callback replacement receives the same live handle. `getTarget()` returns the current target only when the mounted projection matches the controller. Capture that target with the request. Native registration stays host-owned; a normal native tool can invoke a captured command with its native abort signal. The headless controller retains its original type and tools.
|
|
89
48
|
|
|
90
|
-
|
|
49
|
+
| Command | Input beyond `expiresAt` | Applied value |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `inspect` | None | Exact save selection, dirty state, headings, current selection and selected text |
|
|
52
|
+
| `select` | Rich `{kind: 'rich', anchor, head}` or source `{kind: 'source', start, end, direction}` | No value; confirms actual native/DOM selection and focus |
|
|
53
|
+
| `revealHeading` | Zero-based heading `index` from inspection | No value; selects and scrolls to that heading in rich mode |
|
|
91
54
|
|
|
92
|
-
|
|
55
|
+
Rich coordinates are native ProseMirror positions. Source offsets count UTF-16 code units; `direction` is `forward`, `backward` or `none`. Inspection does not return the entire document as selected text. Source inspection has no headings, and source heading navigation returns `unavailable`. Duplicate heading text has distinct indices bound to the current projection.
|
|
93
56
|
|
|
94
|
-
|
|
95
|
-
┌──────────────────────────────────────────┐
|
|
96
|
-
│ @hachej/boring-workspace │
|
|
97
|
-
│ (Dockview chrome, panels, plugins) │
|
|
98
|
-
├──────────────────────────────────────────┤
|
|
99
|
-
│ @hachej/boring-ui-kit ◄── YOU ARE HERE │
|
|
100
|
-
│ │
|
|
101
|
-
│ ┌────────┐ ┌────────┐ ┌────────┐ │
|
|
102
|
-
│ │ Layout │ │ Form │ │Overlay │ │
|
|
103
|
-
│ │Pane, … │ │Input, …│ │Dialog, │ │
|
|
104
|
-
│ └────────┘ └────────┘ └────────┘ │
|
|
105
|
-
│ │
|
|
106
|
-
│ Styled by CSS vars from host: │
|
|
107
|
-
│ – @boring-core/theme.css │
|
|
108
|
-
│ – @boring-workspace/globals.css │
|
|
109
|
-
│ (or your own --boring-* vars) │
|
|
110
|
-
└──────────────────────────────────────────┘
|
|
111
|
-
```
|
|
57
|
+
Commands bind controller instance/epoch, scope, resource/view/base, buffer version, mode, mount and projection identity. An old handle becomes unavailable after unmount or mode change; a serialized old target passed to a replacement handle is stale. Expired requests are stale, cancellation and invalid selection ranges are denied, and missing headings are unavailable. Malformed schema input throws `TypeError`. Selection/navigation use cancellable animation frames; teardown or expiry settles pending work even when a frame never runs.
|
|
112
58
|
|
|
113
|
-
|
|
59
|
+
Read-only documents still support inspection and navigation. These commands do not edit or publish. Reentrant focus/selection handlers can invalidate a request; the command then refuses without restoring old state over newer work. A refusal can follow transient presentation effects. The host must authorize tool exposure and transport separately. DOM and in-process native tests do not qualify browser scrolling, remote authentication or command transport.
|
|
114
60
|
|
|
115
|
-
|
|
116
|
-
// In your app shell (once)
|
|
117
|
-
import "@hachej/boring-workspace/globals.css"
|
|
118
|
-
```
|
|
61
|
+
The default schema includes headings, lists, task lists, quotes, code and text marks. Raw Markdown HTML is literal text, unsafe link protocols are refused by Tiptap, and links do not navigate on click. There are no asset-loading extensions; embedded assets remain separate work. GFM tables are rich: a table whose cells are unchanged is written back in its original text (unpadded `|a|b|`, `:---:` alignment rows, `_emphasis_`, code, links, escaped pipes, empty cells), so such documents pass the rich-safety round trip; an edited table (cell, row, column) is written as padded GFM keeping each column's alignment. The host can supply `className` and its own styles. The [Markdown source recipe](../../registry/README.md) adds scoped default styles and has a real local shadcn installation/restyle test. Hosted registry and browser qualification remain pending.
|
|
119
62
|
|
|
120
|
-
|
|
63
|
+
`test/packages/ui-markdown-editor.test.mjs`, `test/packages/ui-markdown-navigation.test.mjs` and `test/packages/ui-markdown-mounted.test.mjs` and `test/packages/ui-markdown-proposals.test.mjs` drive the real React/Tiptap renderer, DOM selections and SQLite provider. `npm run test:editor-consumer` requires `npm_config_cache` pointing to a writable cache with the pinned archives; it installs actual package tarballs, checks declarations with `skipLibCheck: false`, drives the controls and bundles for browsers without Pi or server filesystem imports. These checks do not establish browser layout, caret, IME or accessibility qualification. Chromium journeys remain pending.
|
|
121
64
|
|
|
122
|
-
|
|
65
|
+
## Optional native chat
|
|
123
66
|
|
|
124
|
-
|
|
67
|
+
`createNativeChatController` from `@hachej/boring-ui-kit/native-chat` borrows an actual native `Conversation` and host-bound `Context`. Supply explicit runtime, scope and principal identity. These labels select a presentation instance and grant no permission. The concrete `controller.conversation` keeps the full native API available. `connect()` owns one native watch, and `dispose()` asynchronously closes that watch without aborting tasks or closing the borrowed Harness.
|
|
125
68
|
|
|
126
|
-
|
|
127
|
-
/* Use :root for normal React apps, :host for Shadow DOM.
|
|
128
|
-
Token values are color/length primitives, not CSS shorthands.
|
|
129
|
-
The canonical defaults ship in @hachej/boring-ui-kit/tokens.css. */
|
|
130
|
-
:root {
|
|
131
|
-
--boring-background: oklch(0.18 0.004 72);
|
|
132
|
-
--boring-foreground: oklch(0.985 0.002 72);
|
|
133
|
-
--boring-border: oklch(0.269 0.006 285.885);
|
|
134
|
-
--boring-radius: 0.625rem;
|
|
135
|
-
/* ... full token set in tokens.css; see styles.css for the utility mapping */
|
|
136
|
-
}
|
|
137
|
-
```
|
|
69
|
+
`setText`, `setAttachments` and `send` retain an immutable selected draft. `beforeSubmit` validates that selection before the first submission and every retry. A refused retry retains the original unknown attempt. Native authorization remains the host boundary's responsibility. A thrown admission stays unknown unless the host can prove it was not admitted. `reconcile()` looks up the original native request. `retrySameRequest()` uses its original request ID and content, including when the user has since edited the composer. `send()` empties the composer at once, like any chat app: the sent text is held by the controller as the pending submission, so text typed before the host confirms is a new draft, never appended to the sent one, and an acknowledgement never clears it. Sending again while a submission is still being confirmed queues the new message in `outbox` and submits it, in order and with its own request ID, once the previous one is admitted. A refused message (validation, or a submission the host proves was not admitted) and any messages waiting behind it go back into the composer, one per line, ahead of the text typed since. A submission whose outcome is unknown stays held for `reconcile()`/`retrySameRequest()`, and further sends are refused until it is resolved. `send(whenBusy, { text, attachments, restore })` sends explicit content without touching the composer (`restore` is what comes back on refusal). This controller retains uncertainty in memory; durable reload of an unacknowledged local draft is not implemented.
|
|
138
70
|
|
|
139
|
-
|
|
71
|
+
The full native `ConversationView` stays available in memory. Remote hosts must separately authorize and redact data before transport; this entry does not replace the authenticated projection boundary (`@hachej/boring-agent/chat-transport` applies the host's `project` to the watch, history pages and submission lookups). The presentation layer is the `pi-chat` registry item ([registry README](../../registry/README.md)); this package ships the headless controller only.
|
|
140
72
|
|
|
141
|
-
|
|
73
|
+
A submit the host refused before admission (status 402 `submission-refused`, for example a metering refusal from `@hachej/boring-agent/metering`) rejects with `SubmissionRefused` carrying the host's message; `remote.definitelyNotAdmitted` recognizes it, so a controller given the spread remote chat shows it as a blocked send (`send.kind === 'blocked'`) with the draft kept, instead of an unknown submission.
|
|
142
74
|
|
|
143
|
-
|
|
144
|
-
@import "@hachej/boring-ui-kit/tokens.css";
|
|
75
|
+
`@hachej/boring-ui-kit/remote-chat` releases its watch stream while the page is hidden (`document.visibilityState`) and reopens it when the page is visible again, because browsers cap concurrent HTTP/1.1 connections per host and one idle stream per background tab starves every other request. The reopened watch starts with a complete view, so the controller keeps reading `connected`; submissions, answers and running turns are separate requests and are unaffected. The watch also survives the network: a stream that ends without an end frame (a proxy or load balancer closed it), or stays silent for 2.5 of the server's announced heartbeat intervals, is reopened with exponential backoff (`reconnect: { baseDelayMs: 1000, maxDelayMs: 30000 }`, with jitter) until the controller stops it. Meanwhile `remote.link` reports reconnecting and a controller given it (spreading the `RemoteChat` into its options does) reads `connection.kind === 'reconnecting'`; the reopened stream starts with a complete view, so nothing is duplicated. A refusal (4xx) or a final end (`retired`, `stopped`, revoked) ends the watch instead. Pass `pauseWhenHidden: false` to keep the stream open, and always call `close()` on a `RemoteChat` the controller never claimed. Node and workers have no `document` and are unaffected.
|
|
145
76
|
|
|
146
|
-
|
|
147
|
-
--boring-primary: oklch(0.72 0.19 150);
|
|
148
|
-
--boring-accent: oklch(0.72 0.19 150);
|
|
149
|
-
}
|
|
150
|
-
```
|
|
77
|
+
`test/packages/ui-native-chat.test.mjs` uses a real native Harness and a fictional local streaming provider.
|
|
151
78
|
|
|
152
|
-
|
|
79
|
+
### Native conversation history
|
|
153
80
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
81
|
+
`loadEarlier()` reads one native page through `Conversation.entries`. `snapshot.history` distinguishes disabled, idle, loading, ready and error states. Each page contains at most 40 native entries in display order. The controller retains one page and an opaque native continuation cursor. The first request captures the greatest observed active entry ID as an inclusive upper bound; later writes do not move that bound. Parent-owned fork entries remain valid native history.
|
|
82
|
+
|
|
83
|
+
A failed read retains the previous page and continuation for retry. `clearHistory()`, head or non-append active-range changes, reconnect, watch closure and disposal invalidate pending responses. Clearing a page does not cancel native work or promise remote read termination. The controller preserves the exact host Context. Page-count limits do not bound message bytes or native storage allocation.
|
|
84
|
+
|
|
85
|
+
Without a custom `source`, history defaults to the borrowed conversation. Set `history: false` to disable it. With a custom source, history is disabled unless the host supplies an explicit `{ id, entries }` native capability for that conversation. The ID must match before and after a read. Hosts remain responsible for current authorization and must dispose or disconnect presentation when its scope is revoked. Expert mode is not redaction.
|
|
86
|
+
|
|
87
|
+
`test/packages/ui-native-chat-history.test.mjs` uses real native reset/head/fork records. `node examples/chat-history.mjs` demonstrates three native pages across a reset without model calls.
|
|
88
|
+
|
|
89
|
+
## Headless canvas document edits
|
|
90
|
+
|
|
91
|
+
Import `parseCanvasDocument`, `applyCanvasEdits`, `CanvasEdit` and `canvasMediaType` from `@hachej/boring-ui-kit/canvas-document`. Pass the host's native `TLStoreSchema`. This entry needs neither a mounted editor nor a resource client. It preserves the selected schema and refuses migrations, unsupported shapes, assets and session records.
|
|
92
|
+
|
|
93
|
+
`parseCanvasDocument(value, schema)` returns a detached, complete native document or throws. It validates native records, document/page presence, parent chains, arrow endpoints, page membership and unique arrow terminals. It performs no normalization or repair.
|
|
94
|
+
|
|
95
|
+
`applyCanvasEdits(document, edits, schema)` returns `{ kind: 'applied', document }` or `{ kind: 'rejected', reason }`. Each edit is one of:
|
|
96
|
+
|
|
97
|
+
- `{ kind: 'create', record }`: create an absent native shape or arrow binding.
|
|
98
|
+
- `{ kind: 'update', record }`: replace an existing record of the same native type. Supply the complete record, including `props` and `meta`. Coordinates are local to its supplied parent.
|
|
99
|
+
- `{ kind: 'remove', id }`: remove an existing shape or binding. Shape removal also deletes descendants, arrows attached to removed shapes, and their bindings. Removing a binding alone preserves both endpoint shapes.
|
|
100
|
+
|
|
101
|
+
A batch may refer to records created later in that batch. Duplicate edits to one ID, missing targets, invalid final graphs and writes removed by the batch's deletion cascade reject the whole batch. Inputs are unchanged. Returned records are detached from the caller's inputs.
|
|
102
|
+
|
|
103
|
+
The caller publishes the candidate through its existing conditional resource operation against the original revision. These functions grant no authority, publish no bytes and promise no native Editor side-effect or geometry equivalence. Local proposal review/adoption remains separate work.
|
|
104
|
+
|
|
105
|
+
## Optional canvas document controller
|
|
106
|
+
|
|
107
|
+
Import `createCanvasController` from `@hachej/boring-ui-kit/canvas`. Supply a borrowed native `TLStore`, an authenticated resource client, a saved snapshot or new target, expected identity and viewer instance/epoch. Install the pinned `@tldraw/editor` and `@tldraw/store` peers and their React peers. The SDK itself depends on Tiptap; selecting canvas does not import Boring's Markdown renderer. Plain Markdown/chat imports remain independent of canvas.
|
|
108
|
+
|
|
109
|
+
For saved sources, use an empty native store or one whose document already matches that source. Construction refuses a different existing document to protect another viewer's draft. Edit through the original `controller.store` API. Capture `controller.actions.selection()` and pass it to `controller.flush(selection)`. Only a matching publication receipt advances the saved base. Late acknowledgements preserve newer shapes. `refresh` preserves dirty work; `discardToRemote` discards only the version selected when that read began. `reconcile` uses the original operation identity after an uncertain acknowledgement. Camera, selection and other session records remain local.
|
|
110
|
+
|
|
111
|
+
Incoming documents require `application/vnd.tldraw+json`, valid UTF-8/JSON, the exact native schema version and supported document records. Assets, custom shapes/bindings and migrations are not yet qualified. A native host can still insert unsupported working records through its borrowed store; the controller reports `problem` and refuses selection/publication until corrected. `readOnly` denies controller publication without changing shared native-store permissions. The owner must configure any rendered editor's read-only behavior separately. Disposal releases owned listeners, not the store or resource client.
|
|
112
|
+
|
|
113
|
+
`npm run test:canvas-consumer` installs actual library tarballs and pinned registry dependencies, type-checks a strict consumer, repeats public native-store/SQLite tests and bundles for browsers. Supply a writable `npm_config_cache` containing those archives. Tests use a DOM animation scheduler and the SDK's supported test mode. Default Node imports retain the SDK's user-sync BroadcastChannel; no server lifecycle guarantee is claimed. Browser journeys, CSS/egress, asset policy and production licensing remain pending under the [canvas owner](../../docs/architecture/CANVAS.md). No license is purchased or provisioned by this package.
|
|
114
|
+
|
|
115
|
+
Canvas consumers set `skipLibCheck: true`, as tldraw apps do. The pinned SDK's own declarations do not check strictly: `@tldraw/utils` imports `lodash.isequal`, `lodash.isequalwith`, `lodash.throttle` and `lodash.uniq` but lists their `@types` only as its devDependencies, and the full SDK's `ArrowShapeUtil.onHandleDrag`/`onTranslateStart` overrides are incompatible with their base under `exactOptionalPropertyTypes`. Neither is a pinning mismatch; both are in tldraw 5.5.2's published declarations. The consumer's own code still checks with `strict` and `exactOptionalPropertyTypes`, and the consumer script reruns with `--skipLibCheck false`, failing on any diagnostic outside `tldraw`/`@tldraw/*` declarations, so Boring's declarations stay library-checked.
|
|
116
|
+
|
|
117
|
+
Native document author records are preserved. They do not grant access or select publication identity. Nonempty author image URLs are refused until the host asset adapter is qualified. Mounting the native editor may add author metadata and make the draft dirty; it does not save automatically.
|
|
118
|
+
|
|
119
|
+
`test/packages/ui-canvas-editor-lifecycle.test.mjs` mounts the actual native `TldrawEditor` in a DOM environment, including React StrictMode. Unmount leaves the concrete controller and an independent native history listener active; subsequent raw loads/puts can still publish through exact saves. Remount preserves the document without implicit publication. Fonts, geometry, browser input and the final Boring renderer are not qualified by this bare-editor test.
|
|
120
|
+
|
|
121
|
+
## Optional React canvas editor
|
|
122
|
+
|
|
123
|
+
Import `CanvasEditor` and `CanvasAssetUrls` from `@hachej/boring-ui-kit/canvas-editor`. Install the pinned `tldraw` peer in addition to the canvas controller peers. Supply `controller`, a complete host-owned `assetUrls` map, and optionally `title`, `height`, `className`, `licenseKey` and the native `onMount` callback. Load `tldraw/tldraw.css` when selecting the canvas route; the native JavaScript loads lazily. Asset URLs must cover all pinned fonts, icons, translations and embed icons, so omitted values cannot silently select default CDN assets.
|
|
124
|
+
|
|
125
|
+
The native toolbar and Save/Check saved version controls use the concrete controller. Lost acknowledgements expose Check save outcome; conflicts preserve local edits and expose explicit discard. Paste/import/upload handlers are disabled until asset/content policy is qualified. Mounting may create native author metadata, but never publishes. Unmount preserves the controller and the native session read-only flag. The host explicitly resets or derives that flag when selecting a later writable session. Use one native editor per store; existing host read-only state remains effective.
|
|
126
|
+
|
|
127
|
+
Run `npm run test:canvas-editor-consumer` for the selected renderer's tarball recipe, actual native/SQLite DOM tests and browser bundle. It uses the same `skipLibCheck` recipe and library-checks Boring's declarations as above, so only the upstream lodash and `ArrowShapeUtil` declaration diagnostics are tolerated. Fictional font/network test boundaries do not qualify browser geometry, real fonts, CSS, egress or production licensing. See the [canvas owner](../../docs/architecture/CANVAS.md).
|
|
128
|
+
|
|
129
|
+
## Canvas proposals
|
|
130
|
+
|
|
131
|
+
`controller.actions.propose(selection, edits, summary)` validates complete native-record edits against the exact selected draft. It retains immutable `before` and `after` documents without changing the borrowed store or publishing. `state.proposals` exposes the review candidates. `actions.accept(id)` adopts a still-current candidate and flushes its actual buffer version. `actions.reject(id)` dismisses it without publication. Reentrant edits, stale bases, pending saves and unknown save outcomes prevent a second publication; later acknowledgements preserve newer drafts.
|
|
159
132
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
## Component Index
|
|
163
|
-
|
|
164
|
-
### Layout & Surfaces
|
|
165
|
-
|
|
166
|
-
| Component | Purpose |
|
|
167
|
-
|-----------|---------|
|
|
168
|
-
| `Pane` | Container for panel surfaces (bordered, rounded, sized for Dockview) |
|
|
169
|
-
| `PaneHeader` | Title bar for panes (title + optional actions) |
|
|
170
|
-
| `PaneBody` | Scrollable content area with proper padding |
|
|
171
|
-
| `PaneToolbar` | Horizontal toolbar for panes (actions, filters, etc.) |
|
|
172
|
-
| `FloatingPanel` | Overlay panel (popovers, contextual menus, inline editors) |
|
|
173
|
-
|
|
174
|
-
### Actions
|
|
175
|
-
|
|
176
|
-
| Component | Purpose |
|
|
177
|
-
|-----------|---------|
|
|
178
|
-
| `Button` | Primary action button with variant + size props |
|
|
179
|
-
| `IconButton` | Icon-only button (tooltips built in) |
|
|
180
|
-
| `ButtonGroup` | Grouped buttons with merged borders |
|
|
181
|
-
| `Toolbar` | Horizontal container for toolbar buttons |
|
|
182
|
-
| `ToolbarButton` | Icon button for toolbar context |
|
|
183
|
-
|
|
184
|
-
### Forms
|
|
185
|
-
|
|
186
|
-
| Component | Purpose |
|
|
187
|
-
|-----------|---------|
|
|
188
|
-
| `Input` | Text input with validation states |
|
|
189
|
-
| `Textarea` | Multi-line text input |
|
|
190
|
-
| `Select` | Dropdown selection (Radix-backed) |
|
|
191
|
-
| `Field` | Form field wrapper (label + input + error) |
|
|
192
|
-
| `FieldLabel` | Accessible form label |
|
|
193
|
-
| `InputGroup` | Input with prepended/appended elements |
|
|
194
|
-
|
|
195
|
-
### Feedback
|
|
133
|
+
`controller.tools.inspect` and `controller.tools.propose` are ordinary expiring presentation commands. The host supplies authorization when adapting them through `@hachej/boring-agent/presentation`. Browser hosts should use the mounted proposal command below to bind the native page and mount as well.
|
|
196
134
|
|
|
197
|
-
|
|
198
|
-
|-----------|---------|
|
|
199
|
-
| `Notice` | Banner alerts (info, warning, error, success) |
|
|
200
|
-
| `EmptyState` | Placeholder for empty content areas |
|
|
201
|
-
| `ErrorState` | Error display with retry action |
|
|
202
|
-
| `Spinner` | Loading indicator |
|
|
203
|
-
| `Skeleton` | Loading placeholder shapes |
|
|
204
|
-
| `StatusBadge` | Colored status indicators |
|
|
205
|
-
| `toast` | Programmatic toast notifications |
|
|
135
|
+
`CanvasEditor` displays changed record fields, including deletion cascades, with escaped JSON Pointer paths. **Accept and save** is a human action. Native read-only mode disables acceptance. Dismiss never publishes. An adopted proposal remains labelled as applied locally; the save status reports publication separately. After an unknown result, reconcile the operation before retrying. **Keep draft and refresh** calls `actions.abandon()` to leave the unconfirmed save behind while retaining local content. It never reports the old operation as saved or replays it.
|
|
206
136
|
|
|
207
|
-
|
|
137
|
+
`npm run canvas:journey:proposals` drives desktop and phone review controls against authenticated fictional HTTP resources and SQLite, including reload, stale edits and delayed acknowledgements. Its native shape edits use the actual Editor API; it does not qualify pointer drawing, real fonts, licensed assets or production egress.
|
|
208
138
|
|
|
209
|
-
|
|
210
|
-
|-----------|---------|
|
|
211
|
-
| `Badge` | Small inline labels |
|
|
212
|
-
| `Chip` | Selectable/removable tags |
|
|
213
|
-
| `InlineCode` | Monospaced inline text |
|
|
214
|
-
| `Kbd` | Keyboard shortcut display |
|
|
215
|
-
| `Avatar` | User/profile image with fallback |
|
|
216
|
-
| `List` | Vertical list with row actions |
|
|
217
|
-
| `DetailList` | Key-value display (label + value pairs) |
|
|
139
|
+
## Mounted canvas commands
|
|
218
140
|
|
|
219
|
-
|
|
141
|
+
Pass `onMountedTools` to `CanvasEditor` to receive `CanvasMountedTools`, or `null` on detach. Capture `tools.getTarget()` before submitting a command. Its resource, revision, buffer version, viewer instance, mount and native page must still match at invocation. Callback replacement retains the live handle; unmount invalidates it. The controller and store remain borrowed.
|
|
220
142
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
| `DropdownMenu` | Context menu (Radix `@radix-ui/react-dropdown-menu`) |
|
|
225
|
-
| `Tooltip` | Hover tooltips (Radix `@radix-ui/react-tooltip`) |
|
|
226
|
-
| `HoverCard` | Hover-reveal cards |
|
|
227
|
-
| `Tabs` | Tabbed navigation (Radix `@radix-ui/react-tabs`) |
|
|
228
|
-
| `Command` | Command palette input (cmdk-based) |
|
|
143
|
+
- `inspect.invoke(target, { expiresAt })` returns the exact save selection, dirty flag, current-page native shapes, selected IDs, camera and viewport. Returned data is a detached frozen snapshot.
|
|
144
|
+
- `select.invoke(target, { expiresAt, shapeIds })` selects existing shapes on the captured page. Duplicate IDs normalize; an empty list clears selection. Missing or other-page IDs refuse before changing selection.
|
|
145
|
+
- `frame.invoke(target, { expiresAt, shapeIds })` immediately frames the shapes' combined native page bounds. It requires a nonempty list and visible geometry, respects camera locking and constraints, and verifies actual viewport containment before reporting applied.
|
|
229
146
|
|
|
230
|
-
|
|
147
|
+
`expiresAt` is an absolute millisecond deadline; pass the native abort signal as the third argument when composing `@hachej/boring-agent/presentation`. Inspect, select and frame work with read-only and dirty documents and never publish. A target change during a reentrant native callback refuses the result, but does not promise rollback of intermediate session effects. Saved-resource edits and remote browser delivery remain separate operations.
|
|
231
148
|
|
|
232
|
-
|
|
233
|
-
|-----------|---------|
|
|
234
|
-
| `SettingsPanel` | Full-page settings layout |
|
|
235
|
-
| `SettingsNav` | Vertical navigation for settings sections |
|
|
236
|
-
| `SettingsActionRow` | Row with label, description, and action control |
|
|
149
|
+
Mounted `tools.propose` accepts the same complete edits as the headless command and retains the selected page/mount target. It refuses native read-only mode and rechecks cancellation, expiry and targeting before returning a proposal. It never accepts or publishes on the agent's behalf.
|
|
237
150
|
|
|
238
|
-
|
|
151
|
+
`test/compatibility/canvas-mounted-native.test.mjs` composes selection and proposals with real native ToolTasks. `npm run canvas:journey:commands` requires `CHROMIUM` and permitted loopback sockets; it checks real desktop/phone viewport geometry with fictional assets. Production fonts, licensing, egress and CSS isolation remain separate qualifications.
|
|
239
152
|
|
|
240
|
-
##
|
|
153
|
+
## Optional HTML source and preview
|
|
241
154
|
|
|
242
|
-
|
|
243
|
-
|---------|------------------------|-----------|----------|---------------|
|
|
244
|
-
| IDE-optimized primitives | ✅ `Pane`, `PaneHeader`, `PaneToolbar` | ❌ None | ❌ None | ❌ None |
|
|
245
|
-
| Zero global CSS | ✅ Host-inherited | ⚠️ Tailwind classes | ✅ Headless | ❌ CSS-in-JS |
|
|
246
|
-
| Ready-made components | ✅ ~40, opinionated | ⚠️ Copy-paste source | ❌ Headless only | ✅ 100+ |
|
|
247
|
-
| Plugin-safe bundle | ✅ Tree-shaked, small | ⚠️ You pick files | ⚠️ Many packages | ❌ Heavy |
|
|
248
|
-
| Settings panels | ✅ `SettingsPanel`, `SettingsNav` | ❌ DIY | ❌ DIY | ❌ DIY |
|
|
249
|
-
| Command palette | ✅ `Command` (cmdk) | ⚠️ Copy-paste | ❌ DIY | ❌ DIY |
|
|
155
|
+
Import `createHtmlController` and the concrete `HtmlController` from `@hachej/boring-ui-kit/html`. Import `HtmlViewer` from `@hachej/boring-ui-kit/html-viewer` only when selecting the React renderer. These entries need no agent, native environment, Tiptap or canvas runtime. The headless controller accepts the same resource identity/client and new or saved source shape as the Markdown controller. Saved resources must be `text/html` with valid UTF-8.
|
|
250
156
|
|
|
251
|
-
|
|
252
|
-
- Building plugin panels for boring-ui workspace apps
|
|
253
|
-
- You want consistent, IDE-style UI without a heavy material library
|
|
254
|
-
- You need pane-specific primitives (`Pane`, `PaneToolbar`)
|
|
157
|
+
`actions.edit`, `actions.selection` and `flush(selection)` preserve exact source bytes through the existing conditional publication contract. Refresh preserves dirty text, discard is explicit, and reconciliation handles lost acknowledgements without another write. Late saves retain later edits. The concrete controller has no mounted HTML tools; the native document tools remain Markdown-specific. Unmount borrows the controller and never saves or disposes it.
|
|
255
158
|
|
|
256
|
-
|
|
257
|
-
- You need a full design system with charts, maps, and data grids (use MUI)
|
|
258
|
-
- You want complete source-level customization (use shadcn/ui directly)
|
|
259
|
-
- You're building a consumer-facing marketing site (use Tailwind components)
|
|
159
|
+
The renderer offers HTML source and preview modes with Save, Refresh, Reconcile save and Discard local edits controls. Preview uses a detached native template and reconstructs fixed HTML formatting elements with escaped text. It removes attributes and discards active, custom, foreign and template subtrees. Links become spans. The generated document has a fixed CSP, an empty iframe sandbox and no-referrer policy. Original nodes are never inserted or adopted. Preview intentionally omits scripts, styles, forms, images, embeds, navigation and external resources; saved source remains exact.
|
|
260
160
|
|
|
261
|
-
|
|
161
|
+
Preview refuses source beyond 262,144 UTF-16 units, traversal beyond 20,000 visited nodes or depth 128, and output beyond 1,048,576 units. Traversal/output limits do not bound peak allocation inside the browser parser. A parser or Trusted Types refusal leaves source viewing and saving available. No permissive Trusted Types policy or raw preview fallback is created. Server rendering does not parse or embed generated markup.
|
|
262
162
|
|
|
263
|
-
|
|
163
|
+
Run `node examples/html-document.mjs` after building for a fictional authenticated resource save. `npm run test:html-consumer` installs isolated tarballs, checks strict concrete declarations, repeats controller/DOM tests and bundles both entries without native/server or heavy viewer implementations. `npm run test:html-registry-consumer` uses the actual shadcn CLI and drives the copied source recipe before and after restyling. These DOM checks do not qualify native parser inertness, iframe/CSP/Trusted Types security, browser input or network isolation. The attempted Chromium journey currently fails at startup under sandbox EPERM. Browser qualification remains open.
|
|
264
164
|
|
|
265
|
-
|
|
266
|
-
|-------|-------|-----|
|
|
267
|
-
| Unstyled components | No utility CSS loaded | Import `@hachej/boring-ui-kit/styles.css` once (it ships the Tailwind utilities the kit needs); a full workspace app already loads `@hachej/boring-workspace/globals.css` |
|
|
268
|
-
| Wrong/missing colors | `--boring-*` tokens not set | Import `@hachej/boring-ui-kit/tokens.css` or set the tokens yourself |
|
|
269
|
-
| `Cannot find module` | Package not built | Run `pnpm --filter @hachej/boring-ui-kit build` |
|
|
270
|
-
| Theme looks wrong | CSS vars overridden elsewhere | Check specificity — your vars should be at `:root` or `html` scope |
|
|
165
|
+
## Optional fixed and derived experiences
|
|
271
166
|
|
|
272
|
-
|
|
167
|
+
Import `validateExperience` from `@hachej/boring-ui-kit/experience/compose` and `Experience` from `@hachej/boring-ui-kit/experience`. Install exact optional peers `@json-render/core@0.21.0`, `@json-render/react@0.21.0`, `zod@4.6.5` and React 19.3.0. The validation entry loads no React runtime, files, Pi or agent. The renderer uses the native json-render catalog, registry, providers and renderer.
|
|
273
168
|
|
|
274
|
-
|
|
169
|
+
Descriptors use format `boring.experience`, version 1 and source `fixed`, `derived` or `generated`. Generated output comes through the optional composer below. The current installed layout kinds are `boring/stack`, `boring/row`, `boring/grid`, `boring/cell` and `boring/generated`, all version 1. Stack/row accept gap `small`, `medium` or `large`, defaulting to medium. Grid adds integer columns from 1 through 4. Cell props contain only a `ref` in `app/cell` form. Every used cell kind/version must appear in the descriptor's `kinds` map. See `examples/fixed-experience.mjs` for a complete descriptor.
|
|
275
170
|
|
|
276
|
-
|
|
277
|
-
- **No data-heavy components** — No charts, data grids, calendar pickers, or Kanban boards. Use `@hachej/boring-workspace/charts` (recharts wrappers) or external libs for those.
|
|
278
|
-
- **No i18n built-in** — All text content (button labels, empty state messages) is plain strings. Wrap externally for localization.
|
|
279
|
-
- **No SSR testing** – Components are client-rendered and rely on browser APIs for portal rendering.
|
|
171
|
+
The host supplies `cells` with `ref`, `kind`, `version`, optional `maxUses` and a stable `render` React component. That component closes over its concrete controller and owning application's operations. Descriptor props and action parameters never reach it. Default maxUses is 1. Registration accepts only one version per kind and one cell per ref. `canView(ref)` is a synchronous host policy callback and must return literal true. The host rerenders Experience when its authorization or cell registrations change. The library checks access again in the cell renderer; unavailable cells show a placeholder. Each host action must still authorize when invoked.
|
|
280
172
|
|
|
281
|
-
|
|
173
|
+
`validateExperience(value, { cells, canView })` returns a detached frozen descriptor or throws. It checks strict per-kind props in addition to native catalog validation, compatible versions, current visibility, connected tree structure, cell counts and bounds. Limits are 200 elements/kinds and depth 24. IDs inherited from Object.prototype are refused because the pinned native renderer caches their signatures incorrectly. Validation does not render cells or execute their operations. Bounds on accepted descriptors do not bound parsing allocation for arbitrary trusted local input.
|
|
282
174
|
|
|
283
|
-
|
|
175
|
+
For a host that owns explicit adoption, `ExperienceRenderer` is also exported from `@hachej/boring-ui-kit/experience`. Pass a descriptor returned by `validateExperience` or the concrete experience controller. It renders the accepted descriptor directly and rechecks each cell's current access. It does not validate arbitrary input or add another acceptance prompt. The host owns asynchronous pre-adoption checks and must rerender when access changes.
|
|
284
176
|
|
|
285
|
-
|
|
286
|
-
A: No. If your app already imports `@hachej/boring-workspace/globals.css`, the UI kit inherits those variables. Only import `@hachej/boring-ui-kit/styles.css` if you're using the kit outside of a boring-ui workspace.
|
|
177
|
+
Experience initially renders the validated layout. A changed descriptor offers Use proposed layout and keeps the current one until acceptance. Invalid proposals leave the current layout intact. An initially unavailable descriptor can be offered after host access or registration becomes available. Explicit acceptance can remount rendered controls; their external controllers remain borrowed and retain dirty text. Unmount never saves or disposes them. Stable host renderer functions avoid incidental React remounts on ordinary rerenders. Browser focus/selection preservation requires separate qualification.
|
|
287
178
|
|
|
288
|
-
|
|
289
|
-
A: Yes. Every component accepts `className` and uses `clsx`/`tailwind-merge` so your Tailwind classes compose correctly. For deeper customization, override CSS variables.
|
|
179
|
+
This first implementation refuses descriptor actions, state expressions, bindings, visibility expressions, watches, repeats, slots and unknown fields. The native provider has built-in actions even without user handlers, so an empty action registry alone would not enforce that boundary. Full named-action routing and live evaluator integrations remain required roadmap work. Arbitrary custom layout schemas are not yet installed by this API.
|
|
290
180
|
|
|
291
|
-
|
|
292
|
-
A: No. While the kit uses Radix UI primitives (like shadcn does), it adds IDE-specific components (`Pane`, `PaneToolbar`, `SettingsPanel`) that shadcn doesn't ship. The kit is authored and maintained, not copied.
|
|
181
|
+
Run `npm run test:experience-consumer` with the writable pinned npm cache. It first installs only UI and registry peers, checks strict declarations and validation, and bundles renderer/validation without native/server/files/heavy viewers. It then installs the files archive explicitly and drives native json-render with a borrowed SQLite document controller. DOM and server-rendering evidence does not qualify actual browser behavior or complete A25-A32.
|
|
293
182
|
|
|
294
|
-
**Q: Can I use this without the rest of boring-ui?**
|
|
295
|
-
A: Yes. The kit is a standalone npm package with no internal boring-ui dependencies. You'll need to provide CSS variables for theming.
|
|
296
183
|
|
|
297
|
-
|
|
298
|
-
A: Not in this package. The kit focuses on UI chrome. Code editing (CodeMirror) and rich text (TipTap) are handled by `@hachej/boring-workspace` and its plugins.
|
|
184
|
+
## Optional layout document and Keep controls
|
|
299
185
|
|
|
300
|
-
|
|
186
|
+
Import `createExperienceDocumentController` and its concrete types from `@hachej/boring-ui-kit/experience/document`. Select `ExperienceDocument` from `@hachej/boring-ui-kit/experience/document-viewer` for React controls. These entries add the existing files publication contract to the fixed/derived grammar. Saved layouts require strict UTF-8 `application/json` with source `fixed`. A new target may start empty or with a validated fixed/derived draft.
|
|
301
187
|
|
|
302
|
-
|
|
188
|
+
`actions.propose(actions.selection(), descriptor)` records a detached offer against the exact resource base and buffer version. It does not change the displayed draft or publish. `actions.adopt(proposalId)` checks that base and current cell visibility, then selects the draft locally and returns its exact save selection. `flush(selection)` is the separate Keep operation. It creates or conditionally replaces the fixed JSON document and returns the provider receipt. A later offer never changes a kept layout without explicit adoption and another Keep.
|
|
303
189
|
|
|
304
|
-
|
|
190
|
+
The renderer exposes Use proposed layout, Dismiss proposed layout, Keep this layout, Refresh layout, Reconcile keep and Discard local layout. Conflicts preserve the local draft. Unknown saves reconcile the original operation without replay. Later adoptions and offers survive late acknowledgements. Explicit refresh retries a previously unavailable layout after access returns. Replacing the concrete controller resets the displayed layout immediately, even when both controllers have the same buffer version. The layout controller and all cell controllers remain borrowed on replacement and unmount.
|
|
305
191
|
|
|
306
|
-
|
|
192
|
+
Cell visibility validation is a current host preflight. A provider that promises atomic authorization of layout publication and its referenced resources must enforce both at its commit boundary. The renderer does not create that cross-resource guarantee. Live model quality, actual browser focus preservation and full A28-A29 remain unqualified. The bounded region/Pin implementation is described below.
|
|
193
|
+
|
|
194
|
+
Run `node examples/experience-keep.mjs` after building for a fictional offer, adoption, conditional creation and receipt lookup. `npm run test:experience-consumer` additionally checks the concrete Keep declarations, real SQLite/controller and React controls, and the resource-enabled browser bundle from isolated installed archives. These are DOM and artifact checks, not a browser journey.
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
## Optional metadata-only composition
|
|
198
|
+
|
|
199
|
+
`composeExperience` from `@hachej/boring-ui-kit/experience/compose` calls the pinned native `experimental_composeSpec`. It accepts json-render's `Experimental_CompositionEvaluator` directly. The headless entry needs no React runtime, files, agent or model SDK. The evaluator belongs to the host; it must authorize the current scope, processing route and budget before every provider attempt. The library checks current candidate visibility and cancellation before each delegation. That preflight does not supply atomic provider admission.
|
|
200
|
+
|
|
201
|
+
Supply `definition` with a static `name`, optional `title`, an `intents` map and kind registrations. Each kind registration has `kind`, a content-free static `description` and a `metadata` map of permitted string values. Each candidate supplies a registered `ref`, matching enumerated `metadata`, optional `root` and optional private `resource` grouping key. References resolve through the existing `cells`/`canView` access contract. Candidate props, bindings, state, action parameters, arbitrary descriptions and identifiers are ignored. The adapter creates opaque candidate, marker and resource IDs; it rebinds actual references only after composition. It never forwards caller context, instructions, initial state or raw request text. The `intent` must select a registered static string.
|
|
202
|
+
|
|
203
|
+
Static descriptions, intent strings and metadata enumerations are trusted host configuration. Their types and length limits cannot establish semantic privacy. The host must keep record content out of those registrations and authorize even enumerated metadata before external processing. Real cell references remain local and appear in validated descriptors; hosts must choose content-free references. The composer adds no operation registry, scheduler or provider engine.
|
|
204
|
+
|
|
205
|
+
Supply explicit `limits` with `maxElements` from 1 through 200, `maxDepth` from 1 through 24 and `maxEvaluations` from 1 through 32. There are at most 128 candidates and registered kinds, 16 metadata fields per kind/candidate, 32 values per field and 32 static intents. Descriptions are at most 500 characters, metadata values 80 and intents 1200. Accepted-input bounds do not bound peak allocation while parsing arbitrary trusted local objects or inside a provider.
|
|
206
|
+
|
|
207
|
+
Iteration captures definitions, candidates, cells and fallback before yielding the first snapshot. It returns frozen `default`, `partial` and `final` snapshots with a composition ID and increasing sequence. Every partial/final is rebuilt without native state and validated against current visibility before exposure. A `default` snapshot carries the current authorized fallback or null, plus a fixed reason of `pending`, `unavailable`, `cancelled` or `limit`. Raw evaluator errors and answer extras never become public diagnostics. Partials are provisional previews; they do not replace the selected layout.
|
|
208
|
+
|
|
209
|
+
The required `signal` belongs to the host. Abort it when the host closes composition. Upstream cancellation releases a pending composition even when an evaluator does not cooperate; it does not prove provider termination. Calling the iterator's `return()` alone cannot interrupt its pending `next()`. On a final result, pass the descriptor to the existing document controller's `propose` with the selection captured before composition. A stale base refuses. Explicit adoption and Keep remain separate human actions.
|
|
210
|
+
|
|
211
|
+
Run `node examples/generated-experience.mjs` after building for deterministic native composition followed by a real conditional SQLite Keep. The isolated experience consumer checks native evaluator declarations, metadata/cancellation behavior, rendered proposal controls and browser bundles from installed archives. Tests also exercise upstream `experimental_createEvaluator` with fictional fetch responses and host budget denial before each request. Those tests establish transport composition without a live Gateway/local model call, model-quality claim, actual browser journey or complete A27/A31 qualification. Full reference application recipes remain required. Region composition and Pin use the bounded API below.
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
## Generated regions and conditional Pin
|
|
215
|
+
|
|
216
|
+
Import `composeExperienceRegion` from `@hachej/boring-ui-kit/experience/regions`. It takes the existing composition inputs plus `descriptor`, `region` and an explicit `trigger` of `open`, `phase` or `request`. It returns the same snapshot union with full merged descriptors. It captures inputs before the first yield, retains the authorized default on refusal, and uses the native composer. Host policy still owns scope, budget, processing admission and cancellation.
|
|
217
|
+
|
|
218
|
+
A `boring/generated` node has a unique `region`, a `candidates` allowlist of refs or registered cell kinds, and saved default `children`. Optional `kinds` restricts both structural and cell kinds. New kinds must already be declared in the fixed descriptor. `maxElements` defaults to 64 and counts descendants, with a maximum of 200. `minWidth` is an integer CSS-pixel minimum from 0 to 4096, default 0. `regenerate` defaults to `['request']`. An optional `prompt` must match the selected registered intent key or its exact static text. Loaded free text is never forwarded as guidance.
|
|
219
|
+
|
|
220
|
+
Nested regions intersect permissions and budgets. Composition subtracts cell uses outside the target region, preserves fixed nodes and ancestors, and allocates fresh descendant IDs. Every partial and final result passes full descriptor validation. The live default remains selected until explicit acceptance. There is no timer or automatic composition on mount.
|
|
221
|
+
|
|
222
|
+
Call `controller.actions.beginRegion(selection, region, trigger)` before host composition. An applied result contains a frozen request. Pass that request and a final descriptor to `actions.proposeRegion`. A newer request for the same region invalidates older results, even at the same buffer version. `actions.adopt` selects the offered region locally. Region adoption preserves the original JSON spelling and whitespace outside replaced element members and the target's children value. Duplicate keys in edited objects are refused.
|
|
223
|
+
|
|
224
|
+
`state.pin` exposes an exact selection only when the draft began clean and contains changes to that region alone. `actions.pin(selection)` uses the existing conditional writer, receipt and reconciliation. It refuses unrelated dirty layout changes; use explicit whole-layout Keep for those. A late receipt retains later local changes. Pin never grants permission to referenced resources.
|
|
225
|
+
|
|
226
|
+
`ExperienceDocument` accepts `onRegenerate(request)` for its **Regenerate** controls. The host owns the evaluator and its abort signal. **Use proposed region**, **Dismiss proposed region** and **Pin this region** separate acceptance and publication. Fixed sibling renderer identities remain stable across region edits. Cell and grid minima inherit the strongest enclosing region minimum; narrow regions can scroll. Borrowed resource controllers survive unmount.
|
|
227
|
+
|
|
228
|
+
Run `node examples/generated-region.mjs` after building for a fictional native composition, offer, adoption and SQLite Pin receipt. Public tests and `npm run test:experience-consumer` exercise strict tarball declarations, real native composition, exact source, request races, DOM identity/selection and unknown-save reconciliation. DOM results do not qualify browser focus, geometry, live model quality or atomic cross-resource authorization.
|
|
229
|
+
|
|
230
|
+
## Consumer-defined text viewers
|
|
231
|
+
|
|
232
|
+
`@hachej/boring-ui-kit/text-buffer` exports the existing `createTextBuffer` and its concrete types for trusted consumer controllers. The root UI entry stays independent of resource providers. The buffer owns the draft, revision, exact flush selection and uncertain-save reconciliation; a custom controller can derive an immutable domain projection from its snapshot.
|
|
233
|
+
|
|
234
|
+
Validate initial and remote content through `readText`, and validate edits before calling `edit`. `observe` is intended for trusted external-store synchronization and bypasses read-only edit checks. `sync` and `replaceText` are synchronous hooks. Refresh checks the buffer again after `replaceText`, so a callback edit or disposal cannot be overwritten by the pending refresh.
|
|
235
|
+
|
|
236
|
+
The [task-list recipe](../../registry/README.md#task-list-viewer) implements a concrete consumer feature, domain operations and renderer. Its native tools share the domain transform and conditional publication provider. They edit saved resources; a mounted human draft keeps its own exact revision and conflict handling.
|
|
237
|
+
|
|
238
|
+
## Opt-in document draft recovery
|
|
239
|
+
|
|
240
|
+
Markdown, HTML, canvas and experience controllers accept `drafts: TextDraftOptions`.
|
|
241
|
+
Import that type and `TextDraftStore` from `@hachej/boring-ui-kit/text-buffer`. The host supplies
|
|
242
|
+
the store, stable `providerInstanceId`, session `signal` and `expiresAt`, and draft
|
|
243
|
+
`retentionMs`. Defaults bound each record to 8 MiB and each listing to 20 records.
|
|
244
|
+
The store must implement the [session and version contract](../../docs/architecture/WEBSITE-INTEGRATION.md#change-review-and-draft-recovery).
|
|
245
|
+
Omitting `drafts` leaves persistence disabled.
|
|
246
|
+
|
|
247
|
+
Edits request a checkpoint. Await `actions.checkpointDraft()` when you need its
|
|
248
|
+
storage result. `state.recovery` distinguishes pending, stored and failed
|
|
249
|
+
checkpoints. `actions.checkDrafts()` reads the current document and returns scoped
|
|
250
|
+
choices. Pass a choice's `selection` to `actions.restoreDraft` or
|
|
251
|
+
`actions.discardDraft`. Both recheck the resource revision, viewer selection and
|
|
252
|
+
stored record. Restore refuses newer local edits or an unsettled save.
|
|
253
|
+
|
|
254
|
+
Restore changes local content. Normal `flush(actions.selection())` still owns
|
|
255
|
+
publication and receipt validation. A matching acknowledgement removes only its
|
|
256
|
+
checkpoint, preserving newer typing and other writers. Store failures remain
|
|
257
|
+
visible without changing publication results. A restored source record can remain
|
|
258
|
+
available if you edit it before saving. Draft recovery does not persist or replay
|
|
259
|
+
an interrupted publication request.
|
|
260
|
+
|
|
261
|
+
Canvas recovery validates native document records and schema. It excludes camera
|
|
262
|
+
and session records. Experience recovery validates fixed descriptor text and clears
|
|
263
|
+
old proposals and Pin authority. Supply `validateDocument` to enforce additional
|
|
264
|
+
host rules when reading or restoring an experience. Custom text controllers can
|
|
265
|
+
supply `drafts.validateText` for their own format.
|
|
266
|
+
|
|
267
|
+
Logout must revoke the host storage session transactionally and purge its payloads,
|
|
268
|
+
then abort its signal. Aborting a signal alone cannot fence another tab's delayed
|
|
269
|
+
write. Ordinary viewer disposal leaves the borrowed store available to other
|
|
270
|
+
viewers. Encryption, retention enforcement and authenticated storage policy remain
|
|
271
|
+
host responsibilities. This option does not implement durable chat drafts.
|
|
272
|
+
|
|
273
|
+
## File tree and filename mentions
|
|
274
|
+
|
|
275
|
+
Bind the authenticated [revision provider](../files/README.md#frontend-revision-provider)
|
|
276
|
+
to the tree:
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
import '@hachej/boring-ui-kit/file-tree.css';
|
|
280
|
+
import { FileTree } from '@hachej/boring-ui-kit/file-tree-view';
|
|
281
|
+
|
|
282
|
+
<FileTree
|
|
283
|
+
revisionProvider={revisionProvider}
|
|
284
|
+
selectedPath={selectedPath}
|
|
285
|
+
onOpen={path => openFile(path)}
|
|
286
|
+
/>
|
|
287
|
+
```
|
|
307
288
|
|
|
308
|
-
|
|
289
|
+
The renderer adapts the official shadcn `sidebar-11` tree. Its shipped CSS works
|
|
290
|
+
without a SidebarProvider or scanning package sources with Tailwind. Attribution
|
|
291
|
+
and the pinned upstream revision are in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
292
|
+
|
|
293
|
+
The tree supports folder expansion, keyboard navigation, filename search,
|
|
294
|
+
pagination, uploads and read-only revision previews. Uploads go to `uploads/`
|
|
295
|
+
by default (`uploadDirectory` overrides it). Duplicate names refuse publication.
|
|
296
|
+
Unknown upload results show **Check upload status** and retain the operation ID;
|
|
297
|
+
checking never republishes the bytes. Keep the local file until the result is
|
|
298
|
+
known. Upload state is in memory and is not a durable cross-reload upload queue.
|
|
299
|
+
|
|
300
|
+
Both components take `locale` (`en` by default, or `fr`; the words are exported as
|
|
301
|
+
`defaultFileTreeLabels` and `frenchFileTreeLabels`) and `labels` to override any word,
|
|
302
|
+
including the accessible names, or to add another language, `upload={false}` and
|
|
303
|
+
`history={false}` to hide those buttons where a host has no uploads or retained
|
|
304
|
+
history, and `refreshKey`: when its value changes the open folders and the current
|
|
305
|
+
search reload, for example after an agent saves a file. A search result shows its
|
|
306
|
+
name with its folder beside it. `AgentWorkspace` passes the same options to its
|
|
307
|
+
Library through `fileTree`, with its own `locale`.
|
|
308
|
+
|
|
309
|
+
For custom layouts, import `createFileTreeController` from
|
|
310
|
+
`@hachej/boring-ui-kit/file-tree`; `FileTreeView` borrows that concrete controller.
|
|
311
|
+
Dispose an owned controller when finished. Disposing it cancels its observations
|
|
312
|
+
without disposing the shared provider. A custom `onOpen` must protect any dirty
|
|
313
|
+
editor before navigating. Both components can mount in a detached window or another
|
|
314
|
+
layout. Pass that window an authenticated provider binding and route `onOpen` to
|
|
315
|
+
the intended viewer; the tree does not own a workspace layout or window transport.
|
|
316
|
+
|
|
317
|
+
The installed `pi-app` recipe accepts the same `revisionProvider` prop on
|
|
318
|
+
`AgentWorkspace`. Its left-pane Library menu opens the tree in the center/chat area.
|
|
319
|
+
Selecting a file opens the right artifact viewer. Chat stays mounted while Library
|
|
320
|
+
is visible, preserving its draft. It also supplies filename results for `@`, attachment
|
|
321
|
+
publication and retained history without separate file callbacks. Its built-in
|
|
322
|
+
text editor navigation asks before discarding unsaved content. The legacy
|
|
323
|
+
`resources` binding remains available for existing hosts; use one binding at a
|
|
324
|
+
time. History previews never replace the editable document.
|