@hachej/boring-ui-kit 0.4.50 → 0.4.52
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/dist/chat-react-controller.d.ts +4 -1
- package/dist/chat-react-controller.js +4 -0
- package/dist/chat-react-remote.d.ts +16 -1
- package/dist/chat-react-remote.js +69 -10
- package/dist/chat-react-session.d.ts +2 -3
- package/dist/chat-react-session.js +38 -4
- package/dist/file-tree-view.js +37 -23
- package/dist/file-tree.d.ts +12 -0
- package/dist/file-tree.js +56 -4
- package/dist/native-chat.d.ts +8 -1
- package/dist/native-chat.js +45 -8
- package/dist/remote-chat.js +51 -5
- package/package.json +2 -2
- package/r/agent-target.json +4 -4
- package/r/app-shell.json +11 -11
- package/r/board-viewer.json +6 -6
- package/r/button.json +1 -1
- package/r/dock.json +1 -1
- package/r/documents.json +12 -12
- package/r/drawing-viewer.json +1 -1
- package/r/feedback.json +6 -6
- package/r/file-tree.json +4 -4
- package/r/highlights.json +3 -3
- package/r/html-viewer.json +3 -3
- package/r/looks-viewer.json +2 -2
- package/r/maquette-viewer.json +1 -1
- package/r/markdown-editor.json +3 -3
- package/r/mockup-viewer.json +1 -1
- package/r/pi-ambient.json +8 -8
- package/r/pi-app-kinds.json +10 -10
- package/r/pi-app.json +11 -11
- package/r/pi-artifacts.json +2 -2
- package/r/pi-chat-approval.json +2 -2
- package/r/pi-chat-attachments.json +1 -1
- package/r/pi-chat-markdown.json +2 -2
- package/r/pi-chat-mentions.json +1 -1
- package/r/pi-chat-pickers.json +1 -1
- package/r/pi-chat-plugins.json +11 -11
- package/r/pi-chat-question.json +1 -1
- package/r/pi-chat-slash.json +2 -2
- package/r/pi-chat-subagent.json +1 -1
- package/r/pi-chat-voice.json +3 -3
- package/r/pi-chat.json +8 -8
- package/r/pi-workspace.json +3 -3
- package/r/placed-agent.json +4 -4
- package/r/provider-setup.json +1 -1
- package/r/registry.json +162 -162
- package/r/shared-chat.json +6 -6
- package/r/spaces.json +1 -1
- package/r/task-list-viewer.json +2 -2
- package/r/version-history.json +2 -2
- package/r/viewer-code.json +2 -2
- package/r/viewer-frame.json +4 -4
- package/r/viewer-html.json +4 -4
- package/r/viewer-image.json +2 -2
- package/r/viewer-markdown.json +5 -5
- package/r/viewer-pdf.json +3 -3
- package/r/viewers.json +6 -6
- package/r/windows.json +1 -1
package/dist/native-chat.d.ts
CHANGED
|
@@ -86,6 +86,12 @@ export interface NativeChatHistorySource {
|
|
|
86
86
|
readonly id: Conversation['id'];
|
|
87
87
|
readonly entries: Conversation['entries'];
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Whether entries older than the view's first one exist, when the host says so. A host that sends a slice of the conversation sets
|
|
91
|
+
* `hasEarlier` on the view it sends (a boolean beside the native fields); without it, a native `head` on a kept entry means earlier entries
|
|
92
|
+
* exist (a reset or compaction starts the view there). `undefined`: the view does not say.
|
|
93
|
+
*/
|
|
94
|
+
export declare function viewHasEarlier(view: ConversationView | undefined): boolean | undefined;
|
|
89
95
|
export type ChatHistoryState = {
|
|
90
96
|
readonly kind: 'disabled' | 'idle';
|
|
91
97
|
} | {
|
|
@@ -195,7 +201,8 @@ export declare function createNativeChatController(options: NativeChatOptions):
|
|
|
195
201
|
/**
|
|
196
202
|
* Sends the composer draft (or `input`) and empties the composer at once. While another send is still being confirmed the message
|
|
197
203
|
* waits in `outbox` and is submitted, in order, after it. A refused message, and any waiting behind it, go back into the composer
|
|
198
|
-
* ahead of what was typed since; nothing is dropped or merged into another message.
|
|
204
|
+
* ahead of what was typed since; nothing is dropped or merged into another message. Sent before any view (while connecting), it is
|
|
205
|
+
* held until the first view arrives and then submitted, in order; if the connection fails first it goes back into the composer. Resolves with the native submission, or
|
|
199
206
|
* undefined when the message was not admitted (refused, unknown, or the controller was disposed).
|
|
200
207
|
*/
|
|
201
208
|
send: (whenBusy?: ChatAttempt["whenBusy"], input?: ChatSendInput) => Promise<Submission | undefined>;
|
package/dist/native-chat.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { awaitWithContext, withCancel } from '@earendil-works/chord/context';
|
|
2
2
|
import { randomUUID } from '@hachej/boring-files/platform';
|
|
3
|
+
/**
|
|
4
|
+
* Whether entries older than the view's first one exist, when the host says so. A host that sends a slice of the conversation sets
|
|
5
|
+
* `hasEarlier` on the view it sends (a boolean beside the native fields); without it, a native `head` on a kept entry means earlier entries
|
|
6
|
+
* exist (a reset or compaction starts the view there). `undefined`: the view does not say.
|
|
7
|
+
*/
|
|
8
|
+
export function viewHasEarlier(view) {
|
|
9
|
+
const said = view?.hasEarlier;
|
|
10
|
+
return typeof said === 'boolean' ? said : undefined;
|
|
11
|
+
}
|
|
3
12
|
function draft(text, attachments, version) {
|
|
4
13
|
if (typeof text !== 'string' || attachments.some(item => typeof item.id !== 'string' || !item.id || typeof item.name !== 'string'
|
|
5
14
|
|| item.content.type !== 'image' || typeof item.content.data !== 'string' || typeof item.content.mimeType !== 'string'))
|
|
@@ -191,6 +200,17 @@ export function createNativeChatController(options) {
|
|
|
191
200
|
publish({ send: { kind: 'admitted', attempt, submissionId, ...(record ? { record } : {}) }, ...(found ? { echo: echoView() } : {}) });
|
|
192
201
|
return true;
|
|
193
202
|
};
|
|
203
|
+
/** Resolves true once a view is there, false if the controller is disposed or its connection fails first. */
|
|
204
|
+
const viewed = () => snapshot.view ? Promise.resolve(true) : new Promise(resolve => {
|
|
205
|
+
const check = () => {
|
|
206
|
+
const failed = snapshot.disposed || snapshot.connection.kind === 'error' || snapshot.connection.kind === 'closed';
|
|
207
|
+
if (snapshot.view || failed) {
|
|
208
|
+
listeners.delete(check);
|
|
209
|
+
resolve(Boolean(snapshot.view) && !snapshot.disposed);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
listeners.add(check);
|
|
213
|
+
});
|
|
194
214
|
/** Messages taken from the composer that wait for the current send (`outbox` in the snapshot). */
|
|
195
215
|
const waiting = [];
|
|
196
216
|
let sending = false;
|
|
@@ -367,15 +387,16 @@ export function createNativeChatController(options) {
|
|
|
367
387
|
if (previous.kind === 'ready' && !previous.hasMore)
|
|
368
388
|
return Promise.resolve();
|
|
369
389
|
if (!historyScan) {
|
|
370
|
-
|
|
390
|
+
// Earlier means below the view's first entry: the view's own entries are on screen already, so the first page never repeats them.
|
|
391
|
+
let firstEntryId;
|
|
371
392
|
for (const entry of snapshot.view?.entries ?? [])
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
if (
|
|
393
|
+
if (firstEntryId === undefined || entry.id < firstEntryId)
|
|
394
|
+
firstEntryId = entry.id;
|
|
395
|
+
if (firstEntryId === undefined || firstEntryId <= 0 || viewHasEarlier(snapshot.view) === false) {
|
|
375
396
|
publish({ history: { kind: 'ready', entries: Object.freeze([]), hasMore: false } });
|
|
376
397
|
return Promise.resolve();
|
|
377
398
|
}
|
|
378
|
-
historyScan = { maxEntryId };
|
|
399
|
+
historyScan = { maxEntryId: firstEntryId - 1 };
|
|
379
400
|
}
|
|
380
401
|
const scan = historyScan, generation = historyGeneration;
|
|
381
402
|
const entries = 'entries' in previous ? previous.entries : Object.freeze([]);
|
|
@@ -445,7 +466,14 @@ export function createNativeChatController(options) {
|
|
|
445
466
|
}
|
|
446
467
|
watch = acquired;
|
|
447
468
|
const ready = snapshot.timing.inputReady === undefined;
|
|
448
|
-
|
|
469
|
+
// Messages sent before any view were counted against none; they are held until now (see `send`), so the first view is their baseline.
|
|
470
|
+
for (const [index, item] of echoes.entries())
|
|
471
|
+
if (item.blind) {
|
|
472
|
+
item.blind = false;
|
|
473
|
+
firstEntries.set(item.key, acquired.value.entries.length);
|
|
474
|
+
item.base = countOf(acquired.value, item.match) + echoes.slice(0, index).filter(other => other.match === item.match).length;
|
|
475
|
+
}
|
|
476
|
+
publish({ connection: live(), view: acquired.value, ...(snapshot.conversationId !== conversation.id ? { conversationId: conversation.id } : {}), ...(ready ? { timing: Object.freeze({ inputReady: mark('input-ready'), sends: Object.freeze([...timings]) }) } : {}) });
|
|
449
477
|
acquired.start(async (view) => {
|
|
450
478
|
if (snapshot.disposed || watch !== acquired)
|
|
451
479
|
return;
|
|
@@ -474,7 +502,8 @@ export function createNativeChatController(options) {
|
|
|
474
502
|
/**
|
|
475
503
|
* Sends the composer draft (or `input`) and empties the composer at once. While another send is still being confirmed the message
|
|
476
504
|
* waits in `outbox` and is submitted, in order, after it. A refused message, and any waiting behind it, go back into the composer
|
|
477
|
-
* ahead of what was typed since; nothing is dropped or merged into another message.
|
|
505
|
+
* ahead of what was typed since; nothing is dropped or merged into another message. Sent before any view (while connecting), it is
|
|
506
|
+
* held until the first view arrives and then submitted, in order; if the connection fails first it goes back into the composer. Resolves with the native submission, or
|
|
478
507
|
* undefined when the message was not admitted (refused, unknown, or the controller was disposed).
|
|
479
508
|
*/
|
|
480
509
|
send: async (whenBusy = 'followUp', input) => {
|
|
@@ -490,13 +519,15 @@ export function createNativeChatController(options) {
|
|
|
490
519
|
}
|
|
491
520
|
if (input?.restore)
|
|
492
521
|
restoreOf.set(selected, draft(input.restore.text, input.restore.attachments ?? [], 0));
|
|
522
|
+
// Connecting with no view yet: the message is held until the first view (below), which is then its echo's baseline.
|
|
523
|
+
const held = !snapshot.view && snapshot.connection.kind === 'connecting';
|
|
493
524
|
// The message is the person's from this moment: it is drawn at once (the echo) and the composer is emptied, before any
|
|
494
525
|
// validation or network call. The server's copy replaces it when it arrives; a refusal puts the words back in the composer.
|
|
495
526
|
// (A steer takes a queued message back and sends it again, so it is not echoed twice.)
|
|
496
527
|
if (whenBusy !== 'steer') {
|
|
497
528
|
const enter = mark('enter');
|
|
498
529
|
const match = selected.text.trim(), key = `m${++echoSeq}`;
|
|
499
|
-
echoes.push({ key, draft: selected, match, base: countOf(snapshot.view, match) + echoes.filter(item => item.match === match).length, at: enter, sentAt: Date.now(), failed: false, admitted: false });
|
|
530
|
+
echoes.push({ key, draft: selected, match, base: countOf(snapshot.view, match) + echoes.filter(item => item.match === match).length, blind: held, at: enter, sentAt: Date.now(), failed: false, admitted: false });
|
|
500
531
|
firstEntries.set(key, snapshot.view?.entries.length ?? 0);
|
|
501
532
|
timings.push(Object.freeze({ key, enter }));
|
|
502
533
|
if (timings.length > 20)
|
|
@@ -512,6 +543,12 @@ export function createNativeChatController(options) {
|
|
|
512
543
|
sending = true;
|
|
513
544
|
publish(cleared);
|
|
514
545
|
try {
|
|
546
|
+
// Sent while connecting: held (shown as the person's own) until the first view, so nothing is submitted before the connection is up.
|
|
547
|
+
// If the connection fails first, it and everything behind it go back into the composer, marked not sent.
|
|
548
|
+
if (held && !await viewed()) {
|
|
549
|
+
restore([selected], { send: { kind: 'blocked', reason: 'Not connected: the message was not sent' } });
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
515
552
|
let next = { draft: selected, whenBusy, resolve: () => { } };
|
|
516
553
|
const first = await dispatch(next);
|
|
517
554
|
let result = first;
|
package/dist/remote-chat.js
CHANGED
|
@@ -14,6 +14,50 @@ export class SubmissionRefused extends Error {
|
|
|
14
14
|
code = 'submission-refused';
|
|
15
15
|
constructor(message) { super(message); this.name = 'SubmissionRefused'; }
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A photo the host sends as a link (an image part with a `url`, `data: ''`, so the first view stays small) is handed out with its
|
|
19
|
+
* `url` resolved against the chat endpoint: a relative link names the endpoint's own route (`?op=image&…`), whatever page the chat is
|
|
20
|
+
* on. The wire replica stays as sent, so later native operations (a field set, a string append) replay on the host's own values; the
|
|
21
|
+
* linked copy is derived after each replay. Values are immutable, so a node maps to one linked node for good: unchanged entries keep
|
|
22
|
+
* their identity and only changed paths are walked again.
|
|
23
|
+
*/
|
|
24
|
+
function imageLinker(base) {
|
|
25
|
+
const linked = new WeakMap();
|
|
26
|
+
const link = (node) => {
|
|
27
|
+
if (!node || typeof node !== 'object')
|
|
28
|
+
return node;
|
|
29
|
+
if (linked.has(node))
|
|
30
|
+
return linked.get(node);
|
|
31
|
+
let out = node;
|
|
32
|
+
if (Array.isArray(node)) {
|
|
33
|
+
const items = node.map(link);
|
|
34
|
+
if (items.some((item, index) => item !== node[index]))
|
|
35
|
+
out = items;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const record = node;
|
|
39
|
+
let copy;
|
|
40
|
+
for (const key of Object.keys(record)) {
|
|
41
|
+
const value = record[key];
|
|
42
|
+
const next = key === 'url' && record.type === 'image' && typeof value === 'string' ? resolved(value) : link(value);
|
|
43
|
+
if (next !== value)
|
|
44
|
+
(copy ??= { ...record })[key] = next;
|
|
45
|
+
}
|
|
46
|
+
if (copy)
|
|
47
|
+
out = copy;
|
|
48
|
+
}
|
|
49
|
+
linked.set(node, out);
|
|
50
|
+
return out;
|
|
51
|
+
};
|
|
52
|
+
// A link that is not a URL is left as sent: the chat draws a placeholder.
|
|
53
|
+
const resolved = (url) => { try {
|
|
54
|
+
return new URL(url, base).href;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return url;
|
|
58
|
+
} };
|
|
59
|
+
return (value) => link(value);
|
|
60
|
+
}
|
|
17
61
|
async function reason(response) {
|
|
18
62
|
try {
|
|
19
63
|
const value = await response.json();
|
|
@@ -30,6 +74,7 @@ async function reason(response) {
|
|
|
30
74
|
*/
|
|
31
75
|
export async function createRemoteChat(options) {
|
|
32
76
|
const base = new URL(options.endpoint, globalThis.location?.href);
|
|
77
|
+
const linkImages = imageLinker(base);
|
|
33
78
|
const send = options.fetch, pollMs = options.pollMs ?? 500;
|
|
34
79
|
const backoff = { base: options.reconnect?.baseDelayMs ?? 1000, max: options.reconnect?.maxDelayMs ?? 30_000 };
|
|
35
80
|
const url = (op, extra = {}) => {
|
|
@@ -265,7 +310,7 @@ export async function createRemoteChat(options) {
|
|
|
265
310
|
settle(end);
|
|
266
311
|
} };
|
|
267
312
|
const handle = {
|
|
268
|
-
get value() { return value; },
|
|
313
|
+
get value() { return linkImages(value); },
|
|
269
314
|
closed,
|
|
270
315
|
lost: () => lost,
|
|
271
316
|
stop: async () => { finish({ reason: 'stopped' }); return closed; },
|
|
@@ -288,7 +333,7 @@ export async function createRemoteChat(options) {
|
|
|
288
333
|
// Native operation batches are replayed with the upstream delta implementation; a view frame resets the replica.
|
|
289
334
|
value = frame.kind === 'view' ? frame.view : applyImmutable(value, frame.ops);
|
|
290
335
|
try {
|
|
291
|
-
await listener(value, [], context);
|
|
336
|
+
await listener(linkImages(value), [], context);
|
|
292
337
|
}
|
|
293
338
|
catch (error) {
|
|
294
339
|
return finish({ reason: 'listener_error', error: error instanceof Error ? error : new Error(String(error)) });
|
|
@@ -345,7 +390,8 @@ export async function createRemoteChat(options) {
|
|
|
345
390
|
cancelRetry();
|
|
346
391
|
setReconnecting(false);
|
|
347
392
|
settle(end);
|
|
348
|
-
|
|
393
|
+
// Stopped before it started (the owner left while the first view was on its way, D-1434): the first watch is still open, close it.
|
|
394
|
+
const live = current ?? (listener ? undefined : first);
|
|
349
395
|
current = undefined;
|
|
350
396
|
void live?.stop();
|
|
351
397
|
};
|
|
@@ -478,11 +524,11 @@ export async function createRemoteChat(options) {
|
|
|
478
524
|
// The controller only reconciles a submission by request ID; no other transaction work crosses the wire.
|
|
479
525
|
commit: async (change, _context) => change({ submissionByRequest: (_conversationId, requestId) => lookup(requestId) }),
|
|
480
526
|
abort: async () => { await post('abort', {}); },
|
|
481
|
-
entries: async (query, limit, cursor) => (await call('entries', {}, { limit: String(limit),
|
|
527
|
+
entries: async (query, limit, cursor) => linkImages((await call('entries', {}, { limit: String(limit),
|
|
482
528
|
...(query.maxEntryId === undefined ? {} : { maxEntryId: String(query.maxEntryId) }),
|
|
483
529
|
...(query.minEntryId === undefined ? {} : { minEntryId: String(query.minEntryId) }),
|
|
484
530
|
...(query.order === undefined ? {} : { order: query.order }),
|
|
485
|
-
...(cursor === undefined ? {} : { cursor: JSON.stringify(cursor) }) })).page,
|
|
531
|
+
...(cursor === undefined ? {} : { cursor: JSON.stringify(cursor) }) })).page),
|
|
486
532
|
};
|
|
487
533
|
const context = BACKGROUND_CONTEXT;
|
|
488
534
|
const close = async () => { const first = unclaimed; unclaimed = undefined; await first?.stop(); };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-ui-kit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.52",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
}
|
|
155
155
|
},
|
|
156
156
|
"peerDependencies": {
|
|
157
|
-
"@hachej/boring-files": "^0.4.
|
|
157
|
+
"@hachej/boring-files": "^0.4.52",
|
|
158
158
|
"@earendil-works/pi-durable": "~1.4.0",
|
|
159
159
|
"react": "^19.0.0",
|
|
160
160
|
"react-dom": "^19.0.0",
|
package/r/agent-target.json
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
"title": "Point and dictate on app data",
|
|
5
5
|
"description": "Tap explicitly marked JSON app context, dictate once and show the same app agent’s native reply without opening chat. Host-controlled presentation; no feedback report or second controller.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-feedback@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
|
+
"@hachej/boring-feedback@^0.4.52",
|
|
9
9
|
"@earendil-works/pi-durable@~1.4.0",
|
|
10
10
|
"react@^19.0.0",
|
|
11
11
|
"react-dom@^19.0.0"
|
|
12
12
|
],
|
|
13
13
|
"registryDependencies": [
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/app-shell.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-chat.json"
|
|
16
16
|
],
|
|
17
17
|
"files": [
|
|
18
18
|
{
|
package/r/app-shell.json
CHANGED
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
"title": "App shell: layouts, list/detail and agent layer",
|
|
5
5
|
"description": "AppShell with three layouts. B \"workspace\" puts a ListDetail on a screen (phone: the list, then the detail as a stack step with Back; wide: a 20rem list and the detail side by side, sidebar as an icon rail). C \"chat\" wraps the app's own AgentWorkspace (untouched, passed as the nav item's render; install the pi-app item yourself) in the same sidebar, account menu and header; it takes no shell agent. A \"screen\": one screen at a time, every screen kept mounted. On a phone bottom tabs (at most four; more give three plus a « Plus » sheet) and an avatar button that opens the account sheet; on a wide screen the shadcn sidebar (icon rail, NavUser). One header per screen through useScreenHeader (title, subtitle, back, up to two actions, the rest behind « … »). The account menu holds « Améliorer l'app » (the improve agent), the app's own entries and sign out. AppSheet is the one bottom sheet (one open at a time, Back closes it). It mounts the AgentLayer: one agent's PiChat above the app (phone: a drawer with peek, half and full snap points; wide: a right panel), the improve agent in the same layer, optional push-to-talk and point mode. Also useAgentContext and readAgentContext (the screen on show travels as `context` with each submit) and useBackLayer, the one Back manager. Built on explicitly pinned Base UI drawer and dropdown-menu recipes, plus the sidebar and avatar of the host's own shadcn style.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-feedback@^0.4.
|
|
8
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-feedback@^0.4.52",
|
|
8
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
9
9
|
"@base-ui/react@^1.9.0",
|
|
10
10
|
"react@^19.0.0",
|
|
11
11
|
"react-dom@^19.0.0",
|
|
12
12
|
"lucide-react@^1.51.0"
|
|
13
13
|
],
|
|
14
14
|
"registryDependencies": [
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
18
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
19
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
20
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
21
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-chat.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-chat-plugins.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-chat-voice.json",
|
|
18
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/dock.json",
|
|
19
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/feedback.json",
|
|
20
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/utils.json",
|
|
21
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/button.json"
|
|
22
22
|
],
|
|
23
23
|
"files": [
|
|
24
24
|
{
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"path": "registry/app-shell/app-shell.tsx",
|
|
62
|
-
"content": "'use client';\n\n// The app shell. Layout A (`screen`): one screen at a time. Compact: bottom tabs (at most four; more give three plus « Plus »), or with `compactNav=\"sheet\"` no bar and the sidebar as a sheet from the header. Expanded:\n// the shadcn sidebar (icon rail plus NavUser). Layout B (`workspace`): the same, with a screen made of a `ListDetail`; the sidebar starts\n// as the icon rail because the list pane is the wide navigation. Layout C (`chat`): the one nav item renders the app's `AgentWorkspace`,\n// untouched, below the same sidebar, account menu and header; there is no shell agent (the page is the chat). The shell imports nothing from pi-app: a layout C app installs the `pi-app` item itself. One header per screen\n// (`useScreenHeader`). The AgentLayer is mounted once, here.\n// Host concern: Pi has no layout; the screen on show travels as the agent's context (useAgentContext -> createPromptContext).\nimport { useEffect, useState, useSyncExternalStore } from 'react';\nimport { afterStacksBack } from '../utils/back';\nimport type { ReactNode } from 'react';\nimport { SidebarProvider } from '@/components/ui/sidebar';\nimport { Button } from '@/components/ui/button';\nimport { AccountAvatar, AccountChoices } from './account';\nimport type { AccountSpec } from './account';\nimport { AgentDockLayout, AgentLayer, AgentLayerProvider } from './agent-layer';\nimport type { AgentSpec } from './agent-layer';\nimport { NoPanelLayout } from './shell-panel';\nimport type { ShellPanel } from './shell-panel';\nimport { ScreenActiveContext, useAgentContext } from './agent-context';\nimport { useIsCompact } from './compact';\nimport { HeaderProvider, ScreenHeader } from './header';\nimport { AppSidebar, TabBar } from './nav';\nimport type { NavItem } from './nav';\nimport { AppSheet } from './app-sheet';\nimport { appShellText } from './text';\nimport { ScreenHeaderSlotContext } from '../utils/screen-header-slot';\nimport type { AppShellLocale, AppShellText } from './text';\n\nexport interface AppShellProps {\n /** A `screen` (tabs), B `workspace` (list and detail), C `chat` (the app's `AgentWorkspace`; no `agent`). */\n readonly layout: 'screen' | 'workspace' | 'chat';\n readonly title: string;\n readonly nav: readonly NavItem[];\n /** Wide-screen navigation: `hover` reveals labels over the page on hover or keyboard focus. Default: `icon` (the native sidebar toggle). */\n readonly rail?: 'icon' | 'hover';\n /** Phone navigation: `tabs` (default) is the bottom tab bar; `sheet` has no bar, the header's leading button opens the sidebar (the nav items, a screen's `sidebar` content and the account) as a sheet. */\n readonly compactNav?: 'tabs' | 'sheet';\n readonly agent?: AgentSpec;\n /** Consume agent/open/input launch parameters on mount (default). `false` leaves the URL and layer untouched. */\n readonly agentLink?: false;\n /** A panel beside the app, opened from the account menu. The product manager is `pmPanel` from the Factory's `platform-ui/pm-panel`. */\n readonly panel?: ShellPanel;\n readonly account?: AccountSpec;\n /** The language of the shell's own words and the layer's (« Se déconnecter », « Plus », « Ouvrir l’assistant »…). The agent's `chatProps.locale` wins when it sets one; else this; else English. */\n readonly locale?: AppShellLocale;\n /** The app's router owns the URL; the shell only reports the choice. */\n readonly route: { readonly id: string; readonly onChange: (id: string) => void };\n /**\n * `false`: no backdrop blur behind dialogs, sheets and drawers (blurring the page costs frames on slow phones), only their dim.\n * Default `true`. Overlays live in a portal, so the shell marks `<html data-overlay-blur=\"off\">` while it is mounted; the app's\n * own CSS drops the blur there (the rule is in registry/README.md: an item's CSS stays inside `.app-shell`).\n */\n readonly overlayBlur?: boolean;\n}\n\n/** `<html data-overlay-blur=\"off\">` while a shell asks for it. */\nfunction useOverlayBlur(blur: boolean) {\n useEffect(() => {\n if (blur || typeof document === 'undefined') return;\n const root = document.documentElement;\n root.dataset.overlayBlur = 'off';\n return () => { delete root.dataset.overlayBlur; };\n }, [blur]);\n}\n\n/** Registers what is on show for the agent. A screen's own `useAgentContext` is mounted later and wins. */\nfunction ShellContext({ name }: { readonly name: string }): null {\n useAgentContext({ name, params: {} });\n return null;\n}\n\nfunction ScreenBody({ item }: { readonly item: NavItem }): ReactNode { return item.render(); }\n\n/** Every screen stays mounted; the ones not on show are `hidden` and do not register a header or a context. */\nfunction Screens({ items, activeId, scrolls, slot }: { readonly items: readonly NavItem[]; readonly activeId: string; readonly scrolls: boolean; readonly slot: HTMLElement | null }) {\n return <div className=\"relative min-h-0 flex-1\">\n {items.map(item => <ScreenActiveContext.Provider key={item.id} value={item.id === activeId}><ScreenHeaderSlotContext.Provider value={item.id === activeId ? slot : null}>\n <div hidden={item.id !== activeId} data-testid={`screen-${item.id}`} className={scrolls ? 'h-full overflow-y-auto overscroll-contain' : 'h-full overflow-hidden'}><ScreenBody item={item} /></div>\n </ScreenHeaderSlotContext.Provider></ScreenActiveContext.Provider>)}\n </div>;\n}\n\nfunction Shell({ compactNav, layout, rail, title, nav, agent, panel, account, route, text }: { readonly compactNav: NonNullable<AppShellProps['compactNav']>; readonly rail: NonNullable<AppShellProps['rail']>; readonly layout: AppShellProps['layout']; readonly title: string; readonly nav: readonly NavItem[]; readonly agent: AgentSpec | undefined; readonly panel: ShellPanel | undefined; readonly account: AccountSpec | undefined; readonly route: AppShellProps['route']; readonly text: AppShellText }) {\n const improve = panel ? panel.label ?? text.improve : undefined;\n const Layout = panel?.Layout ?? NoPanelLayout;\n const compact = useIsCompact();\n const sheet = compact && compactNav === 'sheet';\n const [accountOpen, setAccountOpen] = useState(false);\n // The header's slot for the screen on show (an embedded AgentWorkspace puts its bar's controls there).\n const [slot, setSlot] = useState<HTMLElement | null>(null);\n const active = nav.find(item => item.id === route.id) ?? nav[0];\n if (!active) return null;\n // A nav choice waits for an open detail's stack entry to go back, so the app's router can replace the URL without that Back restoring\n // the detail's. Choosing the screen already on show changes nothing and keeps the detail.\n const choose = (id: string) => { if (id === active.id) route.onChange(id); else afterStacksBack(() => route.onChange(id)); };\n const hasAccount = Boolean(account) || improve;\n const avatar = hasAccount ? <>\n <Button variant=\"ghost\" size=\"icon\" data-testid=\"account-button\" aria-label={account?.name ?? text.account} title={account?.name ?? text.account} onClick={() => setAccountOpen(true)}><AccountAvatar account={account} /></Button>\n <AppSheet open={accountOpen} onOpenChange={setAccountOpen} title={text.account}>\n <AccountChoices account={account} improve={improve} text={text} done={() => setAccountOpen(false)} />\n </AppSheet>\n </> : null;\n // The structure is the same compact or expanded, so a resize never remounts a screen.\n return <SidebarProvider data-testid=\"app-shell\" defaultOpen={rail !== 'hover' && layout === 'screen'} className=\"app-shell h-dvh min-h-0 overflow-hidden\">\n {(!compact || sheet) && <AppSidebar rail={sheet ? 'icon' : rail} title={title} items={nav} activeId={active.id} onChange={choose} agent={agent} account={account} improve={improve} text={text} />}\n <AgentDockLayout>\n <div data-slot=\"sidebar-inset\" className=\"relative flex min-h-0 min-w-0 w-full flex-1 flex-col overflow-hidden bg-background\">\n <Layout screen={active.label} navigation={compact && !sheet ? <TabBar items={nav} activeId={active.id} onChange={choose} text={text} /> : undefined}>\n <ShellContext name={active.label} />\n <ScreenHeader fallbackTitle={active.label} compact={compact} avatar={avatar} menu={sheet} text={text} slotRef={setSlot} />\n <Screens items={nav} activeId={active.id} scrolls={layout === 'screen'} slot={slot} />\n </Layout>\n </div>\n </AgentDockLayout>\n <AgentLayer />\n </SidebarProvider>;\n}\n\nconst never = () => () => {};\n\nexport function AppShell(props: AppShellProps) {\n const { layout, rail = 'icon', compactNav = 'tabs', agent, panel, title, nav, account, route } = props;\n if (layout === 'chat' && agent) throw new Error('AppShell layout \"chat\" takes no `agent`: the page is the chat (use `panel` for the product manager)');\n const text = appShellText(agent?.chatProps?.locale ?? (panel?.locale as AppShellLocale | undefined) ?? props.locale);\n useOverlayBlur(props.overlayBlur ?? true);\n const alone = useSyncExternalStore(never, () => panel?.page?.matches() ?? false, () => false);\n if (alone && panel?.page) return <panel.page.Page config={panel.config} appName={title} locale={props.locale} />;\n return <AgentLayerProvider agent={agent} panel={panel} locale={props.locale} agentLink={props.agentLink} appName={props.title}>\n <HeaderProvider><Shell compactNav={compactNav} layout={layout} rail={rail} title={title} nav={nav} agent={agent} account={account} route={route} panel={panel} text={text} /></HeaderProvider>\n </AgentLayerProvider>;\n}\n",
|
|
62
|
+
"content": "'use client';\n\n// The app shell. Layout A (`screen`): one screen at a time. Compact: bottom tabs (at most four; more give three plus « Plus »), or with `compactNav=\"sheet\"` no bar and the sidebar as a sheet from the header. Expanded:\n// the shadcn sidebar (icon rail plus NavUser). Layout B (`workspace`): the same, with a screen made of a `ListDetail`; the sidebar starts\n// as the icon rail because the list pane is the wide navigation. Layout C (`chat`): the one nav item renders the app's `AgentWorkspace`,\n// untouched, below the same sidebar, account menu and header; there is no shell agent (the page is the chat). The shell imports nothing from pi-app: a layout C app installs the `pi-app` item itself. One header per screen\n// (`useScreenHeader`). The AgentLayer is mounted once, here.\n// Host concern: Pi has no layout; the screen on show travels as the agent's context (useAgentContext -> createPromptContext).\nimport { useEffect, useState, useSyncExternalStore } from 'react';\nimport { afterStacksBack } from '../utils/back';\nimport type { ReactNode } from 'react';\nimport { SidebarProvider } from '@/components/ui/sidebar';\nimport { Button } from '@/components/ui/button';\nimport { AccountAvatar, AccountChoices } from './account';\nimport type { AccountSpec } from './account';\nimport { AgentDockLayout, AgentLayer, AgentLayerProvider } from './agent-layer';\nimport type { AgentSpec } from './agent-layer';\nimport { NoPanelLayout } from './shell-panel';\nimport type { ShellPanel } from './shell-panel';\nimport { ScreenActiveContext, useAgentContext } from './agent-context';\nimport { useIsCompact } from './compact';\nimport { HeaderProvider, ScreenHeader } from './header';\nimport { AppSidebar, TabBar } from './nav';\nimport type { NavItem } from './nav';\nimport { AppSheet } from './app-sheet';\nimport { appShellText } from './text';\nimport { ScreenHeaderSlotContext } from '../utils/screen-header-slot';\nimport type { AppShellLocale, AppShellText } from './text';\n\nexport interface AppShellProps {\n /** A `screen` (tabs), B `workspace` (list and detail), C `chat` (the app's `AgentWorkspace`; no `agent`). */\n readonly layout: 'screen' | 'workspace' | 'chat';\n readonly title: string;\n readonly nav: readonly NavItem[];\n /** Wide-screen navigation: `hover` reveals labels over the page on hover or keyboard focus. Default: `icon` (the native sidebar toggle). */\n readonly rail?: 'icon' | 'hover';\n /** Phone navigation: `tabs` (default) is the bottom tab bar; `sheet` has no bar, the header's leading button opens the sidebar (the nav items, a screen's `sidebar` content and the account) as a sheet. */\n readonly compactNav?: 'tabs' | 'sheet';\n readonly agent?: AgentSpec;\n /** Consume agent/open/input launch parameters on mount (default). `false` leaves the URL and layer untouched. */\n readonly agentLink?: false;\n /** A panel beside the app, opened from the account menu. The product manager is `pmPanel` from the Factory's `platform-ui/pm-panel`. */\n readonly panel?: ShellPanel;\n readonly account?: AccountSpec;\n /** The language of the shell's own words and the layer's (« Se déconnecter », « Plus », « Ouvrir l’assistant »…). The agent's `chatProps.locale` wins when it sets one; else this; else English. */\n readonly locale?: AppShellLocale;\n /** The app's router owns the URL; the shell only reports the choice. */\n readonly route: { readonly id: string; readonly onChange: (id: string) => void };\n /**\n * `false`: no backdrop blur behind dialogs, sheets and drawers (blurring the page costs frames on slow phones), only their dim.\n * Default `true`. Overlays live in a portal, so the shell marks `<html data-overlay-blur=\"off\">` while it is mounted; the app's\n * own CSS drops the blur there (the rule is in registry/README.md: an item's CSS stays inside `.app-shell`).\n */\n readonly overlayBlur?: boolean;\n}\n\n/** `<html data-overlay-blur=\"off\">` while a shell asks for it. */\nfunction useOverlayBlur(blur: boolean) {\n useEffect(() => {\n if (blur || typeof document === 'undefined') return;\n const root = document.documentElement;\n root.dataset.overlayBlur = 'off';\n return () => { delete root.dataset.overlayBlur; };\n }, [blur]);\n}\n\n/** Registers what is on show for the agent. A screen's own `useAgentContext` is mounted later and wins. */\nfunction ShellContext({ name }: { readonly name: string }): null {\n useAgentContext({ name, params: {} });\n return null;\n}\n\nfunction ScreenBody({ item }: { readonly item: NavItem }): ReactNode { return item.render(); }\n\n/** Every screen stays mounted; the ones not on show are `hidden` and do not register a header or a context. */\nfunction Screens({ items, activeId, scrolls, slot }: { readonly items: readonly NavItem[]; readonly activeId: string; readonly scrolls: boolean; readonly slot: HTMLElement | null }) {\n return <div className=\"relative min-h-0 flex-1\">\n {items.map(item => <ScreenActiveContext.Provider key={item.id} value={item.id === activeId}><ScreenHeaderSlotContext.Provider value={item.id === activeId ? slot : null}>\n <div hidden={item.id !== activeId} data-testid={`screen-${item.id}`} className={scrolls ? 'h-full overflow-y-auto overscroll-contain' : 'h-full overflow-hidden'}><ScreenBody item={item} /></div>\n </ScreenHeaderSlotContext.Provider></ScreenActiveContext.Provider>)}\n </div>;\n}\n\nfunction Shell({ compactNav, layout, rail, title, nav, agent, panel, account, route, text }: { readonly compactNav: NonNullable<AppShellProps['compactNav']>; readonly rail: NonNullable<AppShellProps['rail']>; readonly layout: AppShellProps['layout']; readonly title: string; readonly nav: readonly NavItem[]; readonly agent: AgentSpec | undefined; readonly panel: ShellPanel | undefined; readonly account: AccountSpec | undefined; readonly route: AppShellProps['route']; readonly text: AppShellText }) {\n const improve = panel && panel.menu !== false ? panel.label ?? text.improve : undefined;\n const Layout = panel?.Layout ?? NoPanelLayout;\n const compact = useIsCompact();\n const sheet = compact && compactNav === 'sheet';\n const [accountOpen, setAccountOpen] = useState(false);\n // The header's slot for the screen on show (an embedded AgentWorkspace puts its bar's controls there).\n const [slot, setSlot] = useState<HTMLElement | null>(null);\n const active = nav.find(item => item.id === route.id) ?? nav[0];\n if (!active) return null;\n // A nav choice waits for an open detail's stack entry to go back, so the app's router can replace the URL without that Back restoring\n // the detail's. Choosing the screen already on show changes nothing and keeps the detail.\n const choose = (id: string) => { if (id === active.id) route.onChange(id); else afterStacksBack(() => route.onChange(id)); };\n const hasAccount = Boolean(account) || improve;\n const avatar = hasAccount ? <>\n <Button variant=\"ghost\" size=\"icon\" data-testid=\"account-button\" aria-label={account?.name ?? text.account} title={account?.name ?? text.account} onClick={() => setAccountOpen(true)}><AccountAvatar account={account} /></Button>\n <AppSheet open={accountOpen} onOpenChange={setAccountOpen} title={text.account}>\n <AccountChoices account={account} improve={improve} text={text} done={() => setAccountOpen(false)} />\n </AppSheet>\n </> : null;\n // The structure is the same compact or expanded, so a resize never remounts a screen.\n return <SidebarProvider data-testid=\"app-shell\" defaultOpen={rail !== 'hover' && layout === 'screen'} className=\"app-shell h-dvh min-h-0 overflow-hidden\">\n {(!compact || sheet) && <AppSidebar rail={sheet ? 'icon' : rail} title={title} items={nav} activeId={active.id} onChange={choose} agent={agent} account={account} improve={improve} text={text} />}\n <AgentDockLayout>\n <div data-slot=\"sidebar-inset\" className=\"relative flex min-h-0 min-w-0 w-full flex-1 flex-col overflow-hidden bg-background\">\n <Layout screen={active.label} navigation={compact && !sheet ? <TabBar items={nav} activeId={active.id} onChange={choose} text={text} /> : undefined}>\n <ShellContext name={active.label} />\n <ScreenHeader fallbackTitle={active.label} compact={compact} avatar={avatar} menu={sheet} text={text} slotRef={setSlot} />\n <Screens items={nav} activeId={active.id} scrolls={layout === 'screen'} slot={slot} />\n </Layout>\n </div>\n </AgentDockLayout>\n <AgentLayer />\n </SidebarProvider>;\n}\n\nconst never = () => () => {};\n\nexport function AppShell(props: AppShellProps) {\n const { layout, rail = 'icon', compactNav = 'tabs', agent, panel, title, nav, account, route } = props;\n if (layout === 'chat' && agent) throw new Error('AppShell layout \"chat\" takes no `agent`: the page is the chat (use `panel` for the product manager)');\n const text = appShellText(agent?.chatProps?.locale ?? (panel?.locale as AppShellLocale | undefined) ?? props.locale);\n useOverlayBlur(props.overlayBlur ?? true);\n const alone = useSyncExternalStore(never, () => panel?.page?.matches() ?? false, () => false);\n if (alone && panel?.page) return <panel.page.Page config={panel.config} appName={title} locale={props.locale} />;\n return <AgentLayerProvider agent={agent} panel={panel} locale={props.locale} agentLink={props.agentLink} appName={props.title}>\n <HeaderProvider><Shell compactNav={compactNav} layout={layout} rail={rail} title={title} nav={nav} agent={agent} account={account} route={route} panel={panel} text={text} /></HeaderProvider>\n </AgentLayerProvider>;\n}\n",
|
|
63
63
|
"type": "registry:component",
|
|
64
64
|
"target": "components/app-shell/app-shell.tsx"
|
|
65
65
|
},
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
109
|
"path": "registry/app-shell/shell-panel.tsx",
|
|
110
|
-
"content": "'use client';\n\n// The shell's panel place: a second agent or tool beside the app, opened from the account menu. The shell knows only this shape, so an app\n// plugs in what it wants (the product manager is `pmPanel` from the Factory's `platform-ui/pm-panel`) and an app without one installs nothing more.\nimport type { ComponentType, ReactNode } from 'react';\n\nexport interface ShellPanelProviderProps {\n /** `ShellPanel.config`, as the panel wrote it. */\n readonly config: unknown;\n readonly open: boolean;\n readonly close: () => void;\n readonly appName?: string | undefined;\n readonly locale?: string | undefined;\n /** The panel took the whole screen, or gave it back; `collapse` is what Back runs. */\n readonly onFull?: ((full: boolean, collapse: () => void) => void) | undefined;\n readonly children: ReactNode;\n}\n\nexport interface ShellPanelLayoutProps {\n readonly children: ReactNode;\n /** The phone tab bar. The layout hides it while the panel covers the page. */\n readonly navigation?: ReactNode;\n /** The label of the screen on show. */\n readonly screen?: string;\n}\n\nexport interface ShellPanelPageProps {\n readonly config: unknown;\n readonly appName: string;\n readonly locale?: string | undefined;\n}\n\n/** Keep `Provider`, `Layout` and `page.Page` the same across renders: a new component remounts the app. */\nexport interface ShellPanel {\n /** Passed back to `Provider` and `page.Page`. */\n readonly config: unknown;\n /** Wraps the whole shell, so the panel's state outlives its closing. */\n readonly Provider: ComponentType<ShellPanelProviderProps>;\n /** Wraps the screen and draws the panel beside it. */\n readonly Layout: ComponentType<ShellPanelLayoutProps>;\n /** The account menu's row and the layer's title. Default: the shell's « Améliorer l'app ». */\n readonly label?: string;\n /** The panel's language, for the shell's own words when the app has no agent to say it. */\n readonly locale?: string | undefined;\n /** Changes when the panel's conversation changes; a launch link's focus waits on it. */\n readonly identity?: unknown;\n /** The address that shows only the panel, without the shell. */\n readonly page?: { readonly matches: () => boolean; readonly Page: ComponentType<ShellPanelPageProps> };\n}\n\n/** The screen when the app has no panel. */\nexport function NoPanelLayout({ children, navigation }: ShellPanelLayoutProps) {\n return <div className=\"relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">{children}</div>\n <div className=\"shrink-0 md:hidden\">{navigation}</div>\n </div>;\n}\n",
|
|
110
|
+
"content": "'use client';\n\n// The shell's panel place: a second agent or tool beside the app, opened from the account menu. The shell knows only this shape, so an app\n// plugs in what it wants (the product manager is `pmPanel` from the Factory's `platform-ui/pm-panel`) and an app without one installs nothing more.\nimport type { ComponentType, ReactNode } from 'react';\n\nexport interface ShellPanelProviderProps {\n /** `ShellPanel.config`, as the panel wrote it. */\n readonly config: unknown;\n readonly open: boolean;\n readonly close: () => void;\n readonly appName?: string | undefined;\n readonly locale?: string | undefined;\n /** The panel took the whole screen, or gave it back; `collapse` is what Back runs. */\n readonly onFull?: ((full: boolean, collapse: () => void) => void) | undefined;\n readonly children: ReactNode;\n}\n\nexport interface ShellPanelLayoutProps {\n readonly children: ReactNode;\n /** The phone tab bar. The layout hides it while the panel covers the page. */\n readonly navigation?: ReactNode;\n /** The label of the screen on show. */\n readonly screen?: string;\n}\n\nexport interface ShellPanelPageProps {\n readonly config: unknown;\n readonly appName: string;\n readonly locale?: string | undefined;\n}\n\n/** Keep `Provider`, `Layout` and `page.Page` the same across renders: a new component remounts the app. */\nexport interface ShellPanel {\n /** Passed back to `Provider` and `page.Page`. */\n readonly config: unknown;\n /** Wraps the whole shell, so the panel's state outlives its closing. */\n readonly Provider: ComponentType<ShellPanelProviderProps>;\n /** Wraps the screen and draws the panel beside it. */\n readonly Layout: ComponentType<ShellPanelLayoutProps>;\n /** The account menu's row and the layer's title. Default: the shell's « Améliorer l'app ». */\n readonly label?: string;\n /** `false`: no account menu row for the panel, which still mounts (a host that already shows the panel, around a framed app). Default: the row. */\n readonly menu?: false;\n /** The panel's language, for the shell's own words when the app has no agent to say it. */\n readonly locale?: string | undefined;\n /** Changes when the panel's conversation changes; a launch link's focus waits on it. */\n readonly identity?: unknown;\n /** The address that shows only the panel, without the shell. */\n readonly page?: { readonly matches: () => boolean; readonly Page: ComponentType<ShellPanelPageProps> };\n}\n\n/** The screen when the app has no panel. */\nexport function NoPanelLayout({ children, navigation }: ShellPanelLayoutProps) {\n return <div className=\"relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">{children}</div>\n <div className=\"shrink-0 md:hidden\">{navigation}</div>\n </div>;\n}\n",
|
|
111
111
|
"type": "registry:component",
|
|
112
112
|
"target": "components/app-shell/shell-panel.tsx"
|
|
113
113
|
},
|
package/r/board-viewer.json
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
"title": "Board viewer",
|
|
5
5
|
"description": "A board@1 artifact kind for *.board.json. Lazy native tldraw projection of maquette screens, sandboxed HTML mock-ups, goto arrows and notes. Conditional board-file autosave; phone viewing without accidental frame edits. Hosts supply their resource client, asset URLs and license key.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
|
+
"@hachej/boring-files@^0.4.52",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"lucide-react@1.51.0",
|
|
11
11
|
"tldraw@5.5.2"
|
|
12
12
|
],
|
|
13
13
|
"registryDependencies": [
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/maquette-viewer.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/mockup-viewer.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-artifacts.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/utils.json"
|
|
18
18
|
],
|
|
19
19
|
"files": [
|
|
20
20
|
{
|
package/r/button.json
CHANGED
package/r/dock.json
CHANGED
package/r/documents.json
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
"title": "Documents: the Library, a file and its revisions",
|
|
5
5
|
"description": "The workspace's Library as the list, the chosen file in its viewer and its retained revisions beside it (an older one opens read-only). DocumentSheet opens one document over the app for a host's « Voir la fiche », with an optional « Toutes les fiches ». Every read goes through one revisionProvider binding.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
|
+
"@hachej/boring-files@^0.4.52",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"lucide-react@^1.51.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
18
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
19
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
20
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
21
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
22
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/utils.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/button.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-artifacts.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-app.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-app-kinds.json",
|
|
18
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/app-shell.json",
|
|
19
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/file-tree.json",
|
|
20
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/viewer-frame.json",
|
|
21
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/viewer-image.json",
|
|
22
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/viewer-pdf.json"
|
|
23
23
|
],
|
|
24
24
|
"files": [
|
|
25
25
|
{
|
package/r/drawing-viewer.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Drawing viewer",
|
|
5
5
|
"description": "DrawingViewer, drawingKind and describeDrawing: an Excalidraw (.excalidraw) or draw.io (.drawio) file, read-only, drawn by the tool's own renderer on a white page fitted to the shapes: Excalidraw's exportToSvg (its fonts served by the app, never its CDN) and the official diagrams.net viewer, vendored in the kit (@hachej/boring-ui-kit/drawio-viewer), in a sandboxed frame without network. Shown as an image, so nothing in the file runs; an unreadable file shows an inline error. onRead hands the shapes and arrows the renderer read (kind, place, size, label, colours, from and to). Each renderer loads the first time a drawing of its format opens; the kind is not in the pi-app-kinds default list, so apps without drawings install nothing.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0",
|
|
10
10
|
"@excalidraw/excalidraw@0.18.1"
|
package/r/feedback.json
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
"title": "Feedback",
|
|
5
5
|
"description": "Feedback from the chat composer: one Feedback button starts feedback mode on the application page (hover, click to pin a numbered note, hold ⌥ to use the app, optional voice), and one chip carries the notes to the next message. FeedbackBar, NoteBubble, FeedbackChip with its review, useComposerFeedback for the pi-chat composer, and PickerOverlay styles. Optional, not mounted by default: PointButton, AnnotateSheet, FeedbackList and FeedbackReport. Picking, masking, anchoring and placement stay in @hachej/boring-feedback/page and /ui, so restyling cannot weaken them.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-feedback@^0.4.
|
|
8
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
9
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-feedback@^0.4.52",
|
|
8
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
9
|
+
"@hachej/boring-files@^0.4.52",
|
|
10
10
|
"react@^19.0.0",
|
|
11
11
|
"react-dom@^19.0.0",
|
|
12
12
|
"lucide-react@^1.51.0"
|
|
13
13
|
],
|
|
14
14
|
"registryDependencies": [
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
17
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-chat.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/pi-chat-voice.json",
|
|
17
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/utils.json"
|
|
18
18
|
],
|
|
19
19
|
"files": [
|
|
20
20
|
{
|
package/r/file-tree.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"title": "File tree",
|
|
5
5
|
"description": "Browse, search, upload, rename, move, delete and inspect retained versions through one revisionProvider binding. Uses the consumer's shadcn Button, dropdown-menu and dialog with shared version-history rows; Base UI style only.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
|
+
"@hachej/boring-files@^0.4.52",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"lucide-react@^1.51.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/utils.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/version-history.json"
|
|
15
15
|
],
|
|
16
16
|
"files": [
|
|
17
17
|
{
|
package/r/highlights.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"title": "Anchored highlights",
|
|
5
5
|
"description": "Exact Markdown highlights and comments in a conditional workspace sidecar.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
|
+
"@hachej/boring-files@^0.4.52",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/markdown-editor.json"
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
{
|
package/r/html-viewer.json
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
"title": "HTML source and passive preview",
|
|
5
5
|
"description": "Exact HTML source editing with a reconstructed passive preview and borrowed controller. Browser security qualification remains separate.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
|
+
"@hachej/boring-files@^0.4.52",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/button.json"
|
|
14
14
|
],
|
|
15
15
|
"files": [
|
|
16
16
|
{
|
package/r/looks-viewer.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"lucide-react@^1.51.0"
|
|
9
9
|
],
|
|
10
10
|
"registryDependencies": [
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/mockup-viewer.json",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/utils.json"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
package/r/maquette-viewer.json
CHANGED
package/r/markdown-editor.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"title": "Markdown editor",
|
|
5
5
|
"description": "A host-owned styled wrapper over the concrete borrowed Markdown controller.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.52",
|
|
8
|
+
"@hachej/boring-files@^0.4.52",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0",
|
|
11
11
|
"marked@^17.0.6",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"lucide-react@^1.51.0"
|
|
25
25
|
],
|
|
26
26
|
"registryDependencies": [
|
|
27
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
27
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.52/r/button.json"
|
|
28
28
|
],
|
|
29
29
|
"files": [
|
|
30
30
|
{
|
package/r/mockup-viewer.json
CHANGED