@hachej/boring-ui-kit 0.2.5 → 0.3.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.
@@ -4,8 +4,8 @@
4
4
  "title": "Feedback preview",
5
5
  "description": "PreviewBanner: a live, reversible preview of a change in the page (Approve / Discard) over `@hachej/boring-feedback/preview`, whose subagent calls models through a host gateway. Separate from `feedback` so annotation-only apps do not install the model SDK; install `feedback` first (its styles cover the banner).",
6
6
  "dependencies": [
7
- "@hachej/boring-feedback@^0.2.5",
8
- "@earendil-works/pi-ai@~1.1.0",
7
+ "@hachej/boring-feedback@^0.3.1",
8
+ "@earendil-works/pi-ai@~1.2.0",
9
9
  "react@^19.0.0"
10
10
  ],
11
11
  "files": [
package/r/feedback.json CHANGED
@@ -4,16 +4,16 @@
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.2.5",
8
- "@hachej/boring-ui-kit@^0.2.5",
9
- "@hachej/boring-files@^0.2.5",
7
+ "@hachej/boring-feedback@^0.3.1",
8
+ "@hachej/boring-ui-kit@^0.3.1",
9
+ "@hachej/boring-files@^0.3.1",
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.2.5/r/pi-chat.json",
16
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json"
15
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
16
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json"
17
17
  ],
18
18
  "files": [
19
19
  {
@@ -72,7 +72,7 @@
72
72
  },
73
73
  {
74
74
  "path": "registry/feedback/feedback-labels.ts",
75
- "content": "// The words of the feedback controls that cancel a selection, ask a general question and drive point mode, as data. The defaults are English, like the\n// rest of the copied components; `feedbackLabelsFr` is the French set (pass it as `labels` to AnnotateSheet, FeedbackBar,\n// NoteBubble and PointMode). A host may pass any subset: what it leaves out keeps the default.\nexport interface FeedbackLabels {\n /** The annotate sheet's title for a selected zone, and the bubble's accessible name prefix is `note`. */\n readonly title: string;\n readonly close: string;\n /** The button that drops the selected zone and keeps what was typed. */\n readonly cancelSelection: string;\n /** The entry point (bar button, sheet button, general sheet title): a note with no element attached. */\n readonly askGeneral: string;\n /** The sheet's field label for a selected zone / for a general question. */\n readonly changeField: string;\n readonly generalField: string;\n readonly copyReport: string;\n readonly save: string;\n readonly saveAgain: string;\n readonly saving: string;\n readonly saved: string;\n readonly bubblePlaceholder: string;\n /** The pin's one-line desktop hint: holding Alt passes clicks to the app. */\n readonly altHint: string;\n /** The pin's field label, its Add button, and the mic inside the field (record, stop, transcribing, nothing heard, failed). */\n readonly add: string;\n readonly voiceRecord: string;\n readonly voiceStop: string;\n readonly voiceTranscribing: string;\n readonly voiceNothing: string;\n readonly voiceFailed: string;\n /** Announced when the selection was cancelled, so a screen reader hears that the zone is gone. */\n readonly selectionCancelled: string;\n /** Point mode's bar (phones): the hint, the count of picks, Done, Cancel and the \"use the app\" toggle with its hint. */\n readonly pointHint: string;\n readonly pointCount: (count: number) => string;\n readonly pointDone: string;\n readonly pointCancel: string;\n readonly pointUseApp: string;\n readonly pointUsing: string;\n readonly pointBar: string;\n}\n\nexport const feedbackLabels: FeedbackLabels = {\n title: 'Feedback',\n close: 'Close',\n cancelSelection: 'Cancel selection',\n askGeneral: 'Ask a general question',\n changeField: 'What should change?',\n generalField: 'Your question',\n copyReport: 'Copy report',\n save: 'Save',\n saveAgain: 'Save again',\n saving: 'Saving…',\n saved: 'Saved',\n bubblePlaceholder: \"What's wrong here?\",\n altHint: 'Hold ⌥ to use the app',\n add: 'Add note',\n voiceRecord: 'Speak your note',\n voiceStop: 'Stop recording',\n voiceTranscribing: 'Transcribing…',\n voiceNothing: 'Nothing was heard. Your text is kept.',\n voiceFailed: 'The recording could not be transcribed. Your text is kept.',\n selectionCancelled: 'Selection cancelled.',\n pointHint: 'Tap an element to comment',\n pointCount: count => `${count} ${count === 1 ? 'selection' : 'selections'}`,\n pointDone: 'Done',\n pointCancel: 'Cancel',\n pointUseApp: 'Use the app',\n pointUsing: 'Use the app, then come back to comment',\n pointBar: 'Point mode',\n};\n\nexport const feedbackLabelsFr: FeedbackLabels = {\n title: 'Retour',\n close: 'Fermer',\n cancelSelection: 'Annuler la sélection',\n askGeneral: 'Poser une question générale',\n changeField: 'Que faut-il changer ?',\n generalField: 'Votre question',\n copyReport: 'Copier le rapport',\n save: 'Enregistrer',\n saveAgain: 'Enregistrer à nouveau',\n saving: 'Enregistrement…',\n saved: 'Enregistré',\n bubblePlaceholder: \"Qu'est-ce qui ne va pas ici ?\",\n altHint: 'Maintenez ⌥ pour utiliser l’app',\n add: 'Ajouter la note',\n voiceRecord: 'Dicter votre note',\n voiceStop: 'Arrêter l’enregistrement',\n voiceTranscribing: 'Transcription…',\n voiceNothing: 'Rien n’a été entendu. Votre texte est conservé.',\n voiceFailed: 'L’enregistrement n’a pas pu être transcrit. Votre texte est conservé.',\n selectionCancelled: 'Sélection annulée.',\n pointHint: 'Touchez un élément à commenter',\n pointCount: count => `${count} ${count <= 1 ? 'sélection' : 'sélections'}`,\n pointDone: 'Terminé',\n pointCancel: 'Annuler',\n pointUseApp: 'Utiliser l’app',\n pointUsing: 'Utilisez l’app, puis revenez commenter',\n pointBar: 'Mode pointage',\n};\n\n/** The default labels with the host's overrides on top. */\nexport const withLabels = (labels?: Partial<FeedbackLabels>): FeedbackLabels => (labels ? { ...feedbackLabels, ...labels } : feedbackLabels);\n",
75
+ "content": "// The words of the feedback controls that cancel a selection, ask a general question and drive point mode, as data. The defaults are English, like the\n// rest of the copied components; `feedbackLabelsFr` is the French set (pass it as `labels` to AnnotateSheet, FeedbackBar,\n// NoteBubble and PointMode). A host may pass any subset: what it leaves out keeps the default.\nexport interface FeedbackLabels {\n /** The annotate sheet's title for a selected zone, and the bubble's accessible name prefix is `note`. */\n readonly title: string;\n readonly close: string;\n /** The button that drops the selected zone and keeps what was typed. */\n readonly cancelSelection: string;\n /** The entry point (bar button, sheet button, general sheet title): a note with no element attached. */\n readonly askGeneral: string;\n /** The sheet's field label for a selected zone / for a general question. */\n readonly changeField: string;\n readonly generalField: string;\n readonly copyReport: string;\n readonly save: string;\n readonly saveAgain: string;\n readonly saving: string;\n readonly saved: string;\n readonly bubblePlaceholder: string;\n /** The pin's one-line desktop hint: holding Alt passes clicks to the app. */\n readonly altHint: string;\n /** The pin's field label, its Add button, and the mic inside the field (record, stop, transcribing, nothing heard, failed). */\n readonly add: string;\n readonly voiceRecord: string;\n readonly voiceStop: string;\n readonly voiceTranscribing: string;\n readonly voiceNothing: string;\n readonly voiceFailed: string;\n /** Announced when the selection was cancelled, so a screen reader hears that the zone is gone. */\n readonly selectionCancelled: string;\n /** Point mode's bar (phones): the hint, the count of picks, Done, Cancel and the \"use the app\" toggle with its hint. */\n readonly pointHint: string;\n readonly pointCount: (count: number) => string;\n readonly pointDone: string;\n readonly pointCancel: string;\n readonly pointUseApp: string;\n readonly pointUsing: string;\n readonly pointBar: string;\n}\n\nexport const feedbackLabels: FeedbackLabels = {\n title: 'Feedback',\n close: 'Close',\n cancelSelection: 'Cancel selection',\n askGeneral: 'Ask a general question',\n changeField: 'What should change?',\n generalField: 'Your question',\n copyReport: 'Copy report',\n save: 'Save',\n saveAgain: 'Save again',\n saving: 'Saving…',\n saved: 'Saved',\n bubblePlaceholder: \"What's wrong here?\",\n altHint: 'Hold ⌥ to use the app',\n add: 'Add note',\n voiceRecord: 'Speak your note',\n voiceStop: 'Stop recording',\n voiceTranscribing: 'Transcribing…',\n voiceNothing: 'Nothing was heard. Your text is kept.',\n voiceFailed: 'The recording could not be transcribed. Your text is kept.',\n selectionCancelled: 'Selection cancelled.',\n pointHint: 'Tap an element to comment',\n pointCount: count => `${count} ${count === 1 ? 'selection' : 'selections'}`,\n pointDone: 'Done',\n pointCancel: 'Cancel',\n pointUseApp: 'Use the app',\n pointUsing: 'Use the app, then come back to comment',\n pointBar: 'Point mode',\n};\n\nexport const feedbackLabelsFr: FeedbackLabels = {\n title: 'Retour',\n close: 'Fermer',\n cancelSelection: 'Annuler la sélection',\n askGeneral: 'Poser une question générale',\n changeField: 'Que faut-il changer ?',\n generalField: 'Votre question',\n copyReport: 'Copier le rapport',\n save: 'Enregistrer',\n saveAgain: 'Enregistrer à nouveau',\n saving: 'Enregistrement…',\n saved: 'Enregistré',\n bubblePlaceholder: \"Qu’est-ce qui ne va pas ici ?\",\n altHint: 'Maintenez ⌥ pour utiliser l’app',\n add: 'Ajouter la note',\n voiceRecord: 'Dicter votre note',\n voiceStop: 'Arrêter l’enregistrement',\n voiceTranscribing: 'Transcription…',\n voiceNothing: 'Rien n’a été entendu. Votre texte est conservé.',\n voiceFailed: 'L’enregistrement n’a pas pu être transcrit. Votre texte est conservé.',\n selectionCancelled: 'Sélection annulée.',\n pointHint: 'Touchez un élément à commenter',\n pointCount: count => `${count} ${count <= 1 ? 'sélection' : 'sélections'}`,\n pointDone: 'Terminé',\n pointCancel: 'Annuler',\n pointUseApp: 'Utiliser l’app',\n pointUsing: 'Utilisez l’app, puis revenez commenter',\n pointBar: 'Mode pointage',\n};\n\n/** The default labels with the host's overrides on top. */\nexport const withLabels = (labels?: Partial<FeedbackLabels>): FeedbackLabels => (labels ? { ...feedbackLabels, ...labels } : feedbackLabels);\n",
76
76
  "type": "registry:lib",
77
77
  "target": "components/feedback/feedback-labels.ts"
78
78
  },
package/r/file-tree.json CHANGED
@@ -4,14 +4,14 @@
4
4
  "title": "File tree",
5
5
  "description": "Browse, search, upload and inspect retained versions through one revisionProvider binding.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.2.5",
8
- "@hachej/boring-files@^0.2.5",
7
+ "@hachej/boring-ui-kit@^0.3.1",
8
+ "@hachej/boring-files@^0.3.1",
9
9
  "react@^19.0.0",
10
10
  "radix-ui@^1.8.0",
11
11
  "lucide-react@^1.51.0"
12
12
  ],
13
13
  "registryDependencies": [
14
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json"
14
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json"
15
15
  ],
16
16
  "files": [
17
17
  {
@@ -4,8 +4,8 @@
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.2.5",
8
- "@hachej/boring-files@^0.2.5",
7
+ "@hachej/boring-ui-kit@^0.3.1",
8
+ "@hachej/boring-files@^0.3.1",
9
9
  "react@^19.0.0",
10
10
  "react-dom@^19.0.0"
11
11
  ],
@@ -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.2.5",
8
- "@hachej/boring-files@^0.2.5",
7
+ "@hachej/boring-ui-kit@^0.3.1",
8
+ "@hachej/boring-files@^0.3.1",
9
9
  "react@^19.0.0",
10
10
  "react-dom@^19.0.0",
11
11
  "marked@^17.0.6",
package/r/pi-ambient.json CHANGED
@@ -4,18 +4,19 @@
4
4
  "title": "Pi ambient agent",
5
5
  "description": "AmbientChat, a compact floating agent bar that grows into a chat window over an existing page, and AgentNotifications, toasts for finished, failed and waiting tasks (with the one guarded browser Notification helper). A block over the pi-chat item; its in-window artifact panel uses the pi-workspace layout.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.2.5",
7
+ "@hachej/boring-ui-kit@^0.3.1",
8
8
  "react@^19.0.0",
9
9
  "react-dom@^19.0.0",
10
- "@earendil-works/pi-durable@~1.1.0",
11
- "@earendil-works/pi-ai@~1.1.0",
10
+ "@earendil-works/pi-durable@~1.2.0",
11
+ "@earendil-works/pi-ai@~1.2.0",
12
12
  "lucide-react@^1.51.0"
13
13
  ],
14
14
  "registryDependencies": [
15
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
16
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-workspace.json",
17
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
18
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json"
15
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
16
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
17
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-workspace.json",
18
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
19
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json"
19
20
  ],
20
21
  "files": [
21
22
  {
package/r/pi-app.json CHANGED
@@ -4,19 +4,20 @@
4
4
  "title": "Pi agent app",
5
5
  "description": "AgentWorkspace: a whole agent page in one component. Left: the sessions pane (search, New, rename, archive, delete; collapsible, a drawer on a narrow screen). Center: PiChat. Right: the artifact viewers (artifact panel with versions, file viewer, auto-open of new artifacts) in the ArtifactWorkspace panel, a full-screen sheet on a phone. Props are data and callbacks; hooks for the conversations handler (useConversations), the chat transport (useRemoteChat) and saved resources (useSaved).",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.2.5",
8
- "@hachej/boring-files@^0.2.5",
7
+ "@hachej/boring-ui-kit@^0.3.1",
8
+ "@hachej/boring-files@^0.3.1",
9
9
  "react@^19.0.0",
10
10
  "react-dom@^19.0.0",
11
11
  "lucide-react@^1.51.0",
12
12
  "radix-ui@^1.8.0"
13
13
  ],
14
14
  "registryDependencies": [
15
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
16
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-workspace.json",
17
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/viewers.json",
18
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
19
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json"
15
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
16
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
17
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-workspace.json",
18
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/viewers.json",
19
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
20
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json"
20
21
  ],
21
22
  "files": [
22
23
  {
package/r/pi-chat.json CHANGED
@@ -4,23 +4,22 @@
4
4
  "title": "Pi chat",
5
5
  "description": "A complete Tailwind chat over the borrowed native chat controller: composer, messages, one collapsible activity block per turn (tool calls and reasoning), message queue, history, a human-in-the-loop question card, an Approve / Deny card for host tools wrapped with requireApproval optional artifact cards that open generated content in the host's panel, and feedback cards for the opt-in feedback tool (lists, and Show offers that the host page resolves). Chat only: no layout. Owned, editable source; no AI SDK. The optional pi-ambient and pi-workspace blocks build on it.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.2.5",
8
- "@hachej/boring-files@^0.2.5",
7
+ "@hachej/boring-ui-kit@^0.3.1",
8
+ "@hachej/boring-files@^0.3.1",
9
9
  "react@^19.0.0",
10
10
  "react-dom@^19.0.0",
11
11
  "marked@^17.0.6",
12
12
  "entities@^8.1.0",
13
- "@earendil-works/pi-durable@~1.1.0",
14
- "@earendil-works/chord@~1.1.0",
15
- "@earendil-works/pi-ai@~1.1.0",
13
+ "@earendil-works/pi-durable@~1.2.0",
14
+ "@earendil-works/chord@~1.2.0",
15
+ "@earendil-works/pi-ai@~1.2.0",
16
16
  "@modelcontextprotocol/sdk@^1.32.0",
17
17
  "lucide-react@^1.51.0",
18
18
  "use-stick-to-bottom@^1.1.6"
19
19
  ],
20
20
  "registryDependencies": [
21
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json",
22
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
23
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json"
21
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
22
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json"
24
23
  ],
25
24
  "files": [
26
25
  {
@@ -2,14 +2,16 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "pi-workspace",
4
4
  "title": "Pi workspace layout",
5
- "description": "ArtifactWorkspace: the chat with a right-hand panel that slides in at full height, a resizable divider, full screen, a full-screen sheet on a phone and an opt-in float-when-narrow (the host swaps in AmbientChat from pi-ambient). Layout only; what is open is the host's. Pair it with the viewers item for the panel contents. Also dock.tsx: composable docks (DockLayout, Dock, DockMain, useDock) for any host content on the left or right of the main area, each resizable, floatable, closable and a full-screen sheet on a phone.",
5
+ "description": "ArtifactWorkspace: the chat with a right-hand panel that slides in at full height, a resizable divider, full screen, a full-screen sheet on a phone and an opt-in float-when-narrow (the host swaps in AmbientChat from pi-ambient). Layout only; what is open is the host's. Pair it with the viewers item for the panel contents. Also dock.tsx, which re-exports the docks of the dock item (DockLayout, Dock, DockMain, useDock) for any host content on the left or right of the main area, each resizable, floatable, closable and a full-screen sheet on a phone.",
6
6
  "dependencies": [
7
7
  "react@^19.0.0",
8
8
  "react-dom@^19.0.0"
9
9
  ],
10
10
  "registryDependencies": [
11
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json"
11
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
13
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
14
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/dock.json"
13
15
  ],
14
16
  "files": [
15
17
  {
@@ -20,7 +22,7 @@
20
22
  },
21
23
  {
22
24
  "path": "registry/pi-workspace/dock.tsx",
23
- "content": "'use client';\n\nimport { createContext, useCallback, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { KeyboardEvent, PointerEvent, ReactNode } from 'react';\nimport { cn } from '../utils/utils';\n\n/**\n * Composable docks: `DockLayout` is a row holding one `DockMain` (the page's own content, an app for example) and any number of\n * `Dock`s on its left or right (a chat, a list of sessions, a file explorer, an artifact panel). Each dock is resizable from its inner\n * edge and has its own placement: docked, closed, floating (the content renders its own floating surface, for example `AmbientChat`) or,\n * on a narrow layout, a full-screen sheet. `useDock(id)` reaches a dock from anywhere inside the layout, so a button in a chat header can\n * open the files or detach the chat. Layout only: what the docks show is the host's.\n */\n\n/** What a dock is doing now. `floating` keeps the content mounted in a zero-width slot so it can render its own floating surface. */\nexport type DockPlacement = 'docked' | 'floating' | 'sheet' | 'closed';\n\n/** What a dock and the controls around it can do. */\nexport interface DockApi {\n readonly id: string;\n readonly placement: DockPlacement;\n readonly open: boolean;\n readonly floating: boolean;\n /** Whether the layout is wide enough to dock it (false below `narrowBelow`). */\n readonly canDock: boolean;\n readonly width: number;\n readonly setOpen: (next: boolean) => void;\n readonly toggle: () => void;\n /** Float it (only for a `floatable` dock). Opens it if closed. */\n readonly float: () => void;\n /** Put it back beside the main content at its last docked width. Opens it if closed. */\n readonly dock: () => void;\n /** Set the docked width (clamped and remembered like a drag), for example to widen a dock while it shows a document. */\n readonly setWidth: (width: number) => void;\n}\n\nexport interface DockLayoutProps {\n readonly children: ReactNode;\n /** Prefix of the remembered widths, placements and open states (`<storageKey>.<dock id>.width`, …). */\n readonly storageKey?: string;\n /** `session` (default) remembers the layout for the browser tab; `local` across visits. */\n readonly persist?: 'session' | 'local';\n /** The main content is never narrower than this while docks are beside it. */\n readonly minMain?: number;\n readonly className?: string;\n}\n\nexport interface DockProps {\n readonly id: string;\n readonly side: 'left' | 'right';\n /** The dock's content, or a function of its api (to render a floating surface while `floating`, or a close button). Stays mounted in every placement. */\n readonly children: ReactNode | ((api: DockApi) => ReactNode);\n readonly defaultWidth?: number;\n readonly minWidth?: number;\n /** Opt in to floating: dragging below `dragBelow`, Alt+Arrow towards the edge and `api.float` float it, and it floats when the layout is narrow. */\n readonly floatable?: boolean;\n /**\n * Dragging the divider until the dock would be narrower than this shows a hint and releasing floats it (`floatable`) or closes it.\n * Absent: the dock stops at `minWidth`.\n */\n readonly dragBelow?: number;\n /** Below this layout width the dock cannot be docked: it floats (`floatable`), becomes a full-screen sheet while open (`narrow: 'sheet'`) or is hidden. */\n readonly narrowBelow?: number;\n readonly narrow?: 'float' | 'sheet' | 'hide';\n /** Controlled open state (with `onOpenChange`); uncontrolled from `defaultOpen` (true) and remembered otherwise. */\n readonly open?: boolean;\n readonly defaultOpen?: boolean;\n readonly onOpenChange?: (open: boolean) => void;\n /** Floating at first, before the person docks it. */\n readonly defaultFloating?: boolean;\n /**\n * While docked, the dock is the containing block of its content's fixed overlays (a full-screen sheet, a drawer, a viewer's full screen):\n * they cover the dock, not the page. Floating and sheet placements are unaffected.\n */\n readonly contained?: boolean;\n readonly onPlacementChange?: (placement: DockPlacement) => void;\n /** The region's and the divider's accessible names, and the hint shown while a drag would float or close the dock. */\n readonly labels?: { readonly region?: string | undefined; readonly resize?: string | undefined; readonly floatHint?: string | undefined; readonly closeHint?: string | undefined } | undefined;\n readonly className?: string;\n}\n\nexport interface DockMainProps { readonly children: ReactNode; readonly className?: string }\n\ninterface DockRecord { readonly width?: number | undefined; readonly floating?: boolean | undefined; readonly open?: boolean | undefined }\ninterface LayoutContext {\n readonly size: number;\n readonly minMain: number;\n readonly records: Readonly<Record<string, DockRecord>>;\n readonly apis: Readonly<Record<string, DockApi>>;\n readonly update: (id: string, patch: DockRecord) => void;\n readonly publish: (id: string, api: DockApi | undefined) => void;\n readonly read: (id: string) => DockRecord;\n readonly occupied: (except: string) => number;\n}\nconst Layout = createContext<LayoutContext | null>(null);\nconst STEP = 24, BIG_STEP = 96;\n\nfunction store(persist: 'session' | 'local'): Storage | undefined { try { return persist === 'local' ? globalThis.localStorage : globalThis.sessionStorage; } catch { return undefined; } }\n\n/** The row of docks and main content. Measures its own width, so it works inside any container. */\nexport function DockLayout({ children, storageKey = 'boring.dock', persist = 'session', minMain = 360, className }: DockLayoutProps) {\n const root = useRef<HTMLDivElement>(null);\n const [size, setSize] = useState(0);\n const [records, setRecords] = useState<Record<string, DockRecord>>({});\n const [apis, setApis] = useState<Record<string, DockApi>>({});\n const widths = useRef<Record<string, number>>({});\n useLayoutEffect(() => {\n const element = root.current;\n if (!element) return;\n const measure = () => setSize(element.getBoundingClientRect().width);\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n const read = useCallback((id: string): DockRecord => {\n const storage = store(persist);\n const get = (name: string) => { try { return storage?.getItem(`${storageKey}.${id}.${name}`) ?? null; } catch { return null; } };\n const width = Number(get('width')), floating = get('floating'), open = get('open');\n return { width: Number.isFinite(width) && width > 0 ? width : undefined, floating: floating === null ? undefined : floating === '1', open: open === null ? undefined : open === '1' };\n }, [persist, storageKey]);\n const update = useCallback((id: string, patch: DockRecord) => {\n setRecords(current => ({ ...current, [id]: { ...current[id], ...patch } }));\n const storage = store(persist);\n for (const [name, value] of Object.entries(patch)) {\n if (value === undefined) continue;\n try { storage?.setItem(`${storageKey}.${id}.${name}`, typeof value === 'boolean' ? (value ? '1' : '0') : String(Math.round(value))); } catch { /* the layout is a convenience */ }\n }\n }, [persist, storageKey]);\n const publish = useCallback((id: string, api: DockApi | undefined) => {\n widths.current = { ...widths.current, [id]: api?.placement === 'docked' ? api.width : 0 };\n setApis(current => {\n if (api === undefined) { const { [id]: _removed, ...rest } = current; return rest; }\n const previous = current[id];\n return previous && sameApi(previous, api) ? current : { ...current, [id]: api };\n });\n }, []);\n const occupied = useCallback((except: string) => Object.entries(widths.current).reduce((total, [id, width]) => id === except ? total : total + width, 0), []);\n const value = useMemo(() => ({ size, minMain, records, apis, update, publish, read, occupied }), [size, minMain, records, apis, update, publish, read, occupied]);\n return <Layout.Provider value={value}>\n <div ref={root} data-boring=\"dock-layout\" className={cn('relative flex h-full min-h-0 min-w-0 flex-1', className)}>{children}</div>\n </Layout.Provider>;\n}\n\nconst sameApi = (a: DockApi, b: DockApi) => a.placement === b.placement && a.open === b.open && a.width === b.width && a.canDock === b.canDock;\n\n/** The page's own content between the docks; it takes the remaining width. */\nexport function DockMain({ children, className }: DockMainProps) {\n return <main data-boring=\"dock-main\" className={cn('relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden', className)}>{children}</main>;\n}\n\n/** A dock's api from anywhere inside the `DockLayout` (undefined until that dock has rendered). */\nexport function useDock(id: string): DockApi | undefined {\n const layout = useContext(Layout);\n if (!layout) throw new Error('useDock must be used inside a DockLayout');\n return layout.apis[id];\n}\n\n/** One dock beside the main content. Order the children as they should appear: left docks, `DockMain`, right docks. */\nexport function Dock({ id, side, children, defaultWidth = 400, minWidth = 280, floatable = false, dragBelow, narrowBelow, narrow = floatable ? 'float' : 'hide',\n open: controlledOpen, defaultOpen = true, onOpenChange, defaultFloating = false, contained = false, onPlacementChange, labels, className }: DockProps) {\n const layout = useContext(Layout);\n if (!layout) throw new Error('Dock must be used inside a DockLayout');\n const { size, minMain, update, publish, occupied } = layout;\n const [initial] = useState(() => layout.read(id));\n const record = { ...initial, ...layout.records[id] };\n const [dragging, setDragging] = useState(false);\n const [hint, setHint] = useState(false);\n const section = useRef<HTMLElement>(null);\n const controlled = controlledOpen !== undefined;\n const open = controlled ? controlledOpen : record.open ?? defaultOpen;\n const narrowNow = narrowBelow !== undefined && size > 0 && size < narrowBelow;\n const floatingChosen = floatable && (record.floating ?? defaultFloating);\n const placement: DockPlacement = !open ? 'closed'\n : narrowNow ? (narrow === 'float' && floatable ? 'floating' : narrow === 'sheet' ? 'sheet' : 'closed')\n : floatingChosen ? 'floating' : 'docked';\n const max = Math.max(minWidth, size - minMain - occupied(id));\n const width = Math.round(Math.min(Math.max(record.width ?? defaultWidth, minWidth), max));\n\n const setOpen = useCallback((next: boolean) => { if (!controlled) update(id, { open: next }); onOpenChange?.(next); }, [controlled, id, onOpenChange, update]);\n const setFloating = useCallback((next: boolean) => { if (floatable) update(id, { floating: next }); }, [floatable, id, update]);\n const api: DockApi = {\n id, placement, open, floating: placement === 'floating', canDock: !narrowNow, width,\n setOpen, toggle: () => setOpen(!open),\n float: () => { setFloating(true); if (!open) setOpen(true); },\n dock: () => { setFloating(false); if (!open) setOpen(true); },\n setWidth: (next: number) => resize(next),\n };\n const latest = useRef(api); latest.current = api;\n // What `useDock` hands out: the current state, with actions that always reach this render's dock.\n const actions = useMemo(() => ({ setOpen: (next: boolean) => latest.current.setOpen(next), toggle: () => latest.current.toggle(), float: () => latest.current.float(), dock: () => latest.current.dock(), setWidth: (next: number) => latest.current.setWidth(next) }), []);\n useLayoutEffect(() => { publish(id, { ...latest.current, ...actions }); });\n useLayoutEffect(() => () => publish(id, undefined), [id, publish]);\n const reported = useRef(placement);\n useLayoutEffect(() => { if (reported.current !== placement) { reported.current = placement; onPlacementChange?.(placement); } }, [placement, onPlacementChange]);\n\n const resize = (next: number) => update(id, { width: Math.min(Math.max(next, minWidth), max) });\n // Past `dragBelow` the dock keeps its last width; releasing there floats or closes it.\n const drag = (event: PointerEvent<HTMLElement>) => {\n if (!dragging) return;\n const box = section.current?.getBoundingClientRect();\n if (!box) return;\n const target = side === 'left' ? event.clientX - box.left : box.right - event.clientX;\n const below = dragBelow !== undefined && target < dragBelow;\n setHint(below);\n if (!below) resize(target);\n };\n const leave = () => { if (floatable) api.float(); else setOpen(false); };\n const release = (event: PointerEvent<HTMLElement>) => { event.currentTarget.releasePointerCapture?.(event.pointerId); setDragging(false); if (hint) leave(); setHint(false); };\n const keys = (event: KeyboardEvent<HTMLElement>) => {\n const outward = side === 'left' ? 'ArrowLeft' : 'ArrowRight', inward = side === 'left' ? 'ArrowRight' : 'ArrowLeft';\n if (event.altKey && event.key === outward && (floatable || dragBelow !== undefined)) { event.preventDefault(); leave(); return; }\n const step = event.shiftKey ? BIG_STEP : STEP;\n const next = event.key === inward ? width + step : event.key === outward ? width - step : event.key === 'Home' ? minWidth : event.key === 'End' ? max : undefined;\n if (next === undefined) return;\n event.preventDefault(); resize(next);\n };\n\n const docked = placement === 'docked';\n const divider = docked && <div role=\"separator\" tabIndex={0} aria-orientation=\"vertical\" aria-label={labels?.resize ?? 'Resize panel'} aria-valuemin={minWidth} aria-valuemax={max} aria-valuenow={width}\n data-testid={`dock-divider-${id}`} data-dragging={dragging ? 'true' : undefined}\n // No text selection or native drag from the divider: after a double-click reset, the next press must still resize.\n onPointerDown={event => { event.preventDefault(); event.currentTarget.focus(); event.currentTarget.setPointerCapture?.(event.pointerId); setDragging(true); }} onPointerMove={drag}\n onDragStart={event => event.preventDefault()}\n onPointerUp={release} onPointerCancel={() => { setDragging(false); setHint(false); }} onKeyDown={keys} onDoubleClick={() => resize(defaultWidth)}\n className=\"group relative z-10 -mx-1 w-2 shrink-0 cursor-col-resize touch-none select-none outline-none\">\n <span aria-hidden=\"true\" className={cn('absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border transition-[width,background-color] group-hover:w-0.5 group-hover:bg-ring/70 group-focus-visible:w-0.5 group-focus-visible:bg-ring motion-reduce:transition-none', dragging && 'w-0.5 bg-ring')} />\n </div>;\n const hidden = placement === 'closed';\n const panel = <section ref={section} data-boring=\"dock\" data-dock={id} data-side={side} data-placement={placement} aria-label={labels?.region}\n {...(hidden ? { hidden: true, inert: true } : {})}\n style={docked ? { width } : undefined}\n className={cn('relative flex min-h-0 min-w-0 flex-col', docked && 'shrink-0 overflow-hidden bg-background text-foreground', docked && contained && '[contain:layout]',\n placement === 'floating' && 'w-0 flex-none overflow-visible', placement === 'sheet' && 'fixed inset-0 z-50 bg-background text-foreground', hidden && 'hidden',\n dragging && '[&_iframe]:pointer-events-none select-none', className)}>\n {typeof children === 'function' ? children(api) : children}\n {hint && <div role=\"status\" data-testid={`dock-hint-${id}`} className=\"pointer-events-none absolute inset-0 z-20 flex items-center justify-center bg-background/75 p-4 backdrop-blur-[2px]\">\n <span className=\"rounded-full border border-border bg-popover px-4 py-2 text-center text-sm font-medium text-popover-foreground shadow-lg\">\n {floatable ? labels?.floatHint ?? 'Release to float' : labels?.closeHint ?? 'Release to close'}</span>\n </div>}\n </section>;\n return side === 'left' ? <>{panel}{divider}</> : <>{divider}{panel}</>;\n}\n",
25
+ "content": "// The docks live in the `dock` item; this re-export keeps `pi-workspace/dock` imports working unchanged.\nexport * from '../dock/dock';\n",
24
26
  "type": "registry:component",
25
27
  "target": "components/pi-workspace/dock.tsx"
26
28
  }
@@ -9,8 +9,8 @@
9
9
  "lucide-react@^1.51.0"
10
10
  ],
11
11
  "registryDependencies": [
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json",
13
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json"
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
13
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json"
14
14
  ],
15
15
  "files": [
16
16
  {
package/r/registry.json CHANGED
@@ -107,8 +107,7 @@
107
107
  "lucide-react@^1.51.0"
108
108
  ],
109
109
  "registryDependencies": [
110
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json",
111
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json"
110
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json"
112
111
  ],
113
112
  "files": [
114
113
  {
@@ -129,8 +128,8 @@
129
128
  "title": "Markdown editor",
130
129
  "description": "A host-owned styled wrapper over the concrete borrowed Markdown controller.",
131
130
  "dependencies": [
132
- "@hachej/boring-ui-kit@^0.2.5",
133
- "@hachej/boring-files@^0.2.5",
131
+ "@hachej/boring-ui-kit@^0.3.1",
132
+ "@hachej/boring-files@^0.3.1",
134
133
  "react@^19.0.0",
135
134
  "react-dom@^19.0.0",
136
135
  "marked@^17.0.6",
@@ -343,8 +342,8 @@
343
342
  "title": "HTML source and passive preview",
344
343
  "description": "Exact HTML source editing with a reconstructed passive preview and borrowed controller. Browser security qualification remains separate.",
345
344
  "dependencies": [
346
- "@hachej/boring-ui-kit@^0.2.5",
347
- "@hachej/boring-files@^0.2.5",
345
+ "@hachej/boring-ui-kit@^0.3.1",
346
+ "@hachej/boring-files@^0.3.1",
348
347
  "react@^19.0.0",
349
348
  "react-dom@^19.0.0"
350
349
  ],
@@ -435,23 +434,22 @@
435
434
  "title": "Pi chat",
436
435
  "description": "A complete Tailwind chat over the borrowed native chat controller: composer, messages, one collapsible activity block per turn (tool calls and reasoning), message queue, history, a human-in-the-loop question card, an Approve / Deny card for host tools wrapped with requireApproval optional artifact cards that open generated content in the host's panel, and feedback cards for the opt-in feedback tool (lists, and Show offers that the host page resolves). Chat only: no layout. Owned, editable source; no AI SDK. The optional pi-ambient and pi-workspace blocks build on it.",
437
436
  "dependencies": [
438
- "@hachej/boring-ui-kit@^0.2.5",
439
- "@hachej/boring-files@^0.2.5",
437
+ "@hachej/boring-ui-kit@^0.3.1",
438
+ "@hachej/boring-files@^0.3.1",
440
439
  "react@^19.0.0",
441
440
  "react-dom@^19.0.0",
442
441
  "marked@^17.0.6",
443
442
  "entities@^8.1.0",
444
- "@earendil-works/pi-durable@~1.1.0",
445
- "@earendil-works/chord@~1.1.0",
446
- "@earendil-works/pi-ai@~1.1.0",
443
+ "@earendil-works/pi-durable@~1.2.0",
444
+ "@earendil-works/chord@~1.2.0",
445
+ "@earendil-works/pi-ai@~1.2.0",
447
446
  "@modelcontextprotocol/sdk@^1.32.0",
448
447
  "lucide-react@^1.51.0",
449
448
  "use-stick-to-bottom@^1.1.6"
450
449
  ],
451
450
  "registryDependencies": [
452
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json",
453
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
454
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json"
451
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
452
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json"
455
453
  ],
456
454
  "files": [
457
455
  {
@@ -676,18 +674,19 @@
676
674
  "title": "Pi ambient agent",
677
675
  "description": "AmbientChat, a compact floating agent bar that grows into a chat window over an existing page, and AgentNotifications, toasts for finished, failed and waiting tasks (with the one guarded browser Notification helper). A block over the pi-chat item; its in-window artifact panel uses the pi-workspace layout.",
678
676
  "dependencies": [
679
- "@hachej/boring-ui-kit@^0.2.5",
677
+ "@hachej/boring-ui-kit@^0.3.1",
680
678
  "react@^19.0.0",
681
679
  "react-dom@^19.0.0",
682
- "@earendil-works/pi-durable@~1.1.0",
683
- "@earendil-works/pi-ai@~1.1.0",
680
+ "@earendil-works/pi-durable@~1.2.0",
681
+ "@earendil-works/pi-ai@~1.2.0",
684
682
  "lucide-react@^1.51.0"
685
683
  ],
686
684
  "registryDependencies": [
687
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
688
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-workspace.json",
689
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
690
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json"
685
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
686
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
687
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-workspace.json",
688
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
689
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json"
691
690
  ],
692
691
  "files": [
693
692
  {
@@ -707,18 +706,39 @@
707
706
  }
708
707
  ]
709
708
  },
709
+ {
710
+ "name": "dock",
711
+ "type": "registry:block",
712
+ "title": "Docks",
713
+ "description": "Composable docks: DockLayout, Dock, DockMain and useDock lay out any host content on the left or right of the main area, each resizable, floatable, closable and a full-screen sheet on a phone. Layout only; what a dock shows is the host's. Installed by pi-workspace (which re-exports it) and app-shell.",
714
+ "dependencies": [
715
+ "react@^19.0.0"
716
+ ],
717
+ "registryDependencies": [
718
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json"
719
+ ],
720
+ "files": [
721
+ {
722
+ "path": "registry/dock/dock.tsx",
723
+ "type": "registry:component",
724
+ "target": "components/dock/dock.tsx"
725
+ }
726
+ ]
727
+ },
710
728
  {
711
729
  "name": "pi-workspace",
712
730
  "type": "registry:block",
713
731
  "title": "Pi workspace layout",
714
- "description": "ArtifactWorkspace: the chat with a right-hand panel that slides in at full height, a resizable divider, full screen, a full-screen sheet on a phone and an opt-in float-when-narrow (the host swaps in AmbientChat from pi-ambient). Layout only; what is open is the host's. Pair it with the viewers item for the panel contents. Also dock.tsx: composable docks (DockLayout, Dock, DockMain, useDock) for any host content on the left or right of the main area, each resizable, floatable, closable and a full-screen sheet on a phone.",
732
+ "description": "ArtifactWorkspace: the chat with a right-hand panel that slides in at full height, a resizable divider, full screen, a full-screen sheet on a phone and an opt-in float-when-narrow (the host swaps in AmbientChat from pi-ambient). Layout only; what is open is the host's. Pair it with the viewers item for the panel contents. Also dock.tsx, which re-exports the docks of the dock item (DockLayout, Dock, DockMain, useDock) for any host content on the left or right of the main area, each resizable, floatable, closable and a full-screen sheet on a phone.",
715
733
  "dependencies": [
716
734
  "react@^19.0.0",
717
735
  "react-dom@^19.0.0"
718
736
  ],
719
737
  "registryDependencies": [
720
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
721
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json"
738
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
739
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
740
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
741
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/dock.json"
722
742
  ],
723
743
  "files": [
724
744
  {
@@ -739,19 +759,20 @@
739
759
  "title": "Pi agent app",
740
760
  "description": "AgentWorkspace: a whole agent page in one component. Left: the sessions pane (search, New, rename, archive, delete; collapsible, a drawer on a narrow screen). Center: PiChat. Right: the artifact viewers (artifact panel with versions, file viewer, auto-open of new artifacts) in the ArtifactWorkspace panel, a full-screen sheet on a phone. Props are data and callbacks; hooks for the conversations handler (useConversations), the chat transport (useRemoteChat) and saved resources (useSaved).",
741
761
  "dependencies": [
742
- "@hachej/boring-ui-kit@^0.2.5",
743
- "@hachej/boring-files@^0.2.5",
762
+ "@hachej/boring-ui-kit@^0.3.1",
763
+ "@hachej/boring-files@^0.3.1",
744
764
  "react@^19.0.0",
745
765
  "react-dom@^19.0.0",
746
766
  "lucide-react@^1.51.0",
747
767
  "radix-ui@^1.8.0"
748
768
  ],
749
769
  "registryDependencies": [
750
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
751
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-workspace.json",
752
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/viewers.json",
753
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
754
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json"
770
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
771
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
772
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-workspace.json",
773
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/viewers.json",
774
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
775
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json"
755
776
  ],
756
777
  "files": [
757
778
  {
@@ -827,18 +848,19 @@
827
848
  "title": "App shell: layouts, list/detail and agent layer",
828
849
  "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 the shadcn drawer, sidebar, avatar and dropdown-menu of the host's own style (Base UI variants).",
829
850
  "dependencies": [
830
- "@hachej/boring-feedback@^0.2.5",
831
- "@hachej/boring-ui-kit@^0.2.5",
851
+ "@hachej/boring-feedback@^0.3.1",
852
+ "@hachej/boring-ui-kit@^0.3.1",
832
853
  "@base-ui/react@^1.8.0",
833
854
  "react@^19.0.0",
834
855
  "react-dom@^19.0.0",
835
856
  "lucide-react@^1.51.0"
836
857
  ],
837
858
  "registryDependencies": [
838
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
839
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/feedback.json",
840
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
841
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json",
859
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
860
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/dock.json",
861
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/feedback.json",
862
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
863
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json",
842
864
  "drawer",
843
865
  "sidebar",
844
866
  "avatar",
@@ -902,6 +924,11 @@
902
924
  "type": "registry:component",
903
925
  "target": "components/app-shell/header.tsx"
904
926
  },
927
+ {
928
+ "path": "registry/app-shell/header-actions.tsx",
929
+ "type": "registry:component",
930
+ "target": "components/app-shell/header-actions.tsx"
931
+ },
905
932
  {
906
933
  "path": "registry/app-shell/nav.tsx",
907
934
  "type": "registry:component",
@@ -935,8 +962,8 @@
935
962
  "title": "Viewers",
936
963
  "description": "The standard viewer top bar (title, status, viewer controls, Refresh, Share, Copy, Download, Open in new tab, Close, overflow menu) with Tailwind panes over it: the Markdown editor with a formatting toolbar and prose styling, the passive HTML preview and source editor, an image viewer with zoom and pan, and a PDF viewer. Owned, editable source over the borrowed controllers; no AI SDK.",
937
964
  "dependencies": [
938
- "@hachej/boring-ui-kit@^0.2.5",
939
- "@hachej/boring-files@^0.2.5",
965
+ "@hachej/boring-ui-kit@^0.3.1",
966
+ "@hachej/boring-files@^0.3.1",
940
967
  "react@^19.0.0",
941
968
  "react-dom@^19.0.0",
942
969
  "marked@^17.0.6",
@@ -955,9 +982,9 @@
955
982
  "lucide-react@^1.51.0"
956
983
  ],
957
984
  "registryDependencies": [
958
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json",
959
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json",
960
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/button.json"
985
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
986
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json",
987
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/button.json"
961
988
  ],
962
989
  "files": [
963
990
  {
@@ -1580,8 +1607,8 @@
1580
1607
  "lucide-react@^1.51.0"
1581
1608
  ],
1582
1609
  "registryDependencies": [
1583
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json",
1584
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json"
1610
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json",
1611
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json"
1585
1612
  ],
1586
1613
  "files": [
1587
1614
  {
@@ -1597,9 +1624,9 @@
1597
1624
  "title": "Feedback",
1598
1625
  "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.",
1599
1626
  "dependencies": [
1600
- "@hachej/boring-feedback@^0.2.5",
1601
- "@hachej/boring-ui-kit@^0.2.5",
1602
- "@hachej/boring-files@^0.2.5",
1627
+ "@hachej/boring-feedback@^0.3.1",
1628
+ "@hachej/boring-ui-kit@^0.3.1",
1629
+ "@hachej/boring-files@^0.3.1",
1603
1630
  "react@^19.0.0",
1604
1631
  "react-dom@^19.0.0",
1605
1632
  "lucide-react@^1.51.0"
@@ -2382,8 +2409,8 @@
2382
2409
  }
2383
2410
  },
2384
2411
  "registryDependencies": [
2385
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/pi-chat.json",
2386
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/utils.json"
2412
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/pi-chat.json",
2413
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/utils.json"
2387
2414
  ]
2388
2415
  },
2389
2416
  {
@@ -2392,8 +2419,8 @@
2392
2419
  "title": "Feedback preview",
2393
2420
  "description": "PreviewBanner: a live, reversible preview of a change in the page (Approve / Discard) over `@hachej/boring-feedback/preview`, whose subagent calls models through a host gateway. Separate from `feedback` so annotation-only apps do not install the model SDK; install `feedback` first (its styles cover the banner).",
2394
2421
  "dependencies": [
2395
- "@hachej/boring-feedback@^0.2.5",
2396
- "@earendil-works/pi-ai@~1.1.0",
2422
+ "@hachej/boring-feedback@^0.3.1",
2423
+ "@earendil-works/pi-ai@~1.2.0",
2397
2424
  "react@^19.0.0"
2398
2425
  ],
2399
2426
  "files": [
@@ -2410,8 +2437,8 @@
2410
2437
  "title": "Consumer task-list viewer",
2411
2438
  "description": "A consumer-defined task-list schema and controller over the shared text buffer, with a restylable borrowed React renderer.",
2412
2439
  "dependencies": [
2413
- "@hachej/boring-ui-kit@^0.2.5",
2414
- "@hachej/boring-files@^0.2.5",
2440
+ "@hachej/boring-ui-kit@^0.3.1",
2441
+ "@hachej/boring-files@^0.3.1",
2415
2442
  "react@^19.0.0",
2416
2443
  "react-dom@^19.0.0",
2417
2444
  "zod@^4.6.5"
@@ -2477,14 +2504,14 @@
2477
2504
  "title": "File tree",
2478
2505
  "description": "Browse, search, upload and inspect retained versions through one revisionProvider binding.",
2479
2506
  "dependencies": [
2480
- "@hachej/boring-ui-kit@^0.2.5",
2481
- "@hachej/boring-files@^0.2.5",
2507
+ "@hachej/boring-ui-kit@^0.3.1",
2508
+ "@hachej/boring-files@^0.3.1",
2482
2509
  "react@^19.0.0",
2483
2510
  "radix-ui@^1.8.0",
2484
2511
  "lucide-react@^1.51.0"
2485
2512
  ],
2486
2513
  "registryDependencies": [
2487
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.2.5/r/theme.json"
2514
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.1/r/theme.json"
2488
2515
  ],
2489
2516
  "files": [
2490
2517
  {
@@ -4,8 +4,8 @@
4
4
  "title": "Consumer task-list viewer",
5
5
  "description": "A consumer-defined task-list schema and controller over the shared text buffer, with a restylable borrowed React renderer.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.2.5",
8
- "@hachej/boring-files@^0.2.5",
7
+ "@hachej/boring-ui-kit@^0.3.1",
8
+ "@hachej/boring-files@^0.3.1",
9
9
  "react@^19.0.0",
10
10
  "react-dom@^19.0.0",
11
11
  "zod@^4.6.5"