@hachej/boring-ui-kit 0.3.0 → 0.3.2

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.
@@ -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.3.0/r/pi-chat.json",
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json"
11
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-chat.json",
13
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
14
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/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.3.0/r/theme.json",
13
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json"
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
13
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/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.3.0/r/theme.json",
111
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json"
110
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/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.3.0",
133
- "@hachej/boring-files@^0.3.0",
131
+ "@hachej/boring-ui-kit@^0.3.2",
132
+ "@hachej/boring-files@^0.3.2",
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.3.0",
347
- "@hachej/boring-files@^0.3.0",
345
+ "@hachej/boring-ui-kit@^0.3.2",
346
+ "@hachej/boring-files@^0.3.2",
348
347
  "react@^19.0.0",
349
348
  "react-dom@^19.0.0"
350
349
  ],
@@ -435,8 +434,8 @@
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.3.0",
439
- "@hachej/boring-files@^0.3.0",
437
+ "@hachej/boring-ui-kit@^0.3.2",
438
+ "@hachej/boring-files@^0.3.2",
440
439
  "react@^19.0.0",
441
440
  "react-dom@^19.0.0",
442
441
  "marked@^17.0.6",
@@ -449,9 +448,8 @@
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.3.0/r/theme.json",
453
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json",
454
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/button.json"
451
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
452
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/button.json"
455
453
  ],
456
454
  "files": [
457
455
  {
@@ -676,7 +674,7 @@
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.3.0",
677
+ "@hachej/boring-ui-kit@^0.3.2",
680
678
  "react@^19.0.0",
681
679
  "react-dom@^19.0.0",
682
680
  "@earendil-works/pi-durable@~1.2.0",
@@ -684,10 +682,11 @@
684
682
  "lucide-react@^1.51.0"
685
683
  ],
686
684
  "registryDependencies": [
687
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/pi-chat.json",
688
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/pi-workspace.json",
689
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json",
690
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/button.json"
685
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
686
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-chat.json",
687
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-workspace.json",
688
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
689
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/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.2/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.3.0/r/pi-chat.json",
721
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json"
738
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
739
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-chat.json",
740
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
741
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/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.3.0",
743
- "@hachej/boring-files@^0.3.0",
762
+ "@hachej/boring-ui-kit@^0.3.2",
763
+ "@hachej/boring-files@^0.3.2",
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.3.0/r/pi-chat.json",
751
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/pi-workspace.json",
752
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/viewers.json",
753
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json",
754
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/button.json"
770
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
771
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-chat.json",
772
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-workspace.json",
773
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/viewers.json",
774
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
775
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/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.3.0",
831
- "@hachej/boring-ui-kit@^0.3.0",
832
- "@base-ui/react@^1.8.0",
851
+ "@hachej/boring-feedback@^0.3.2",
852
+ "@hachej/boring-ui-kit@^0.3.2",
853
+ "@base-ui/react@^1.9.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.3.0/r/pi-chat.json",
839
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/feedback.json",
840
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json",
841
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/button.json",
859
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-chat.json",
860
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/dock.json",
861
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/feedback.json",
862
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
863
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/button.json",
842
864
  "drawer",
843
865
  "sidebar",
844
866
  "avatar",
@@ -882,6 +904,11 @@
882
904
  "type": "registry:hook",
883
905
  "target": "components/app-shell/use-visual-viewport.ts"
884
906
  },
907
+ {
908
+ "path": "registry/app-shell/shell-layer.ts",
909
+ "type": "registry:lib",
910
+ "target": "components/app-shell/shell-layer.ts"
911
+ },
885
912
  {
886
913
  "path": "registry/app-shell/text.ts",
887
914
  "type": "registry:lib",
@@ -902,6 +929,11 @@
902
929
  "type": "registry:component",
903
930
  "target": "components/app-shell/header.tsx"
904
931
  },
932
+ {
933
+ "path": "registry/app-shell/header-actions.tsx",
934
+ "type": "registry:component",
935
+ "target": "components/app-shell/header-actions.tsx"
936
+ },
905
937
  {
906
938
  "path": "registry/app-shell/nav.tsx",
907
939
  "type": "registry:component",
@@ -935,8 +967,8 @@
935
967
  "title": "Viewers",
936
968
  "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
969
  "dependencies": [
938
- "@hachej/boring-ui-kit@^0.3.0",
939
- "@hachej/boring-files@^0.3.0",
970
+ "@hachej/boring-ui-kit@^0.3.2",
971
+ "@hachej/boring-files@^0.3.2",
940
972
  "react@^19.0.0",
941
973
  "react-dom@^19.0.0",
942
974
  "marked@^17.0.6",
@@ -955,9 +987,9 @@
955
987
  "lucide-react@^1.51.0"
956
988
  ],
957
989
  "registryDependencies": [
958
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/theme.json",
959
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json",
960
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/button.json"
990
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
991
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
992
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/button.json"
961
993
  ],
962
994
  "files": [
963
995
  {
@@ -1580,8 +1612,8 @@
1580
1612
  "lucide-react@^1.51.0"
1581
1613
  ],
1582
1614
  "registryDependencies": [
1583
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/theme.json",
1584
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json"
1615
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
1616
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json"
1585
1617
  ],
1586
1618
  "files": [
1587
1619
  {
@@ -1597,9 +1629,9 @@
1597
1629
  "title": "Feedback",
1598
1630
  "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
1631
  "dependencies": [
1600
- "@hachej/boring-feedback@^0.3.0",
1601
- "@hachej/boring-ui-kit@^0.3.0",
1602
- "@hachej/boring-files@^0.3.0",
1632
+ "@hachej/boring-feedback@^0.3.2",
1633
+ "@hachej/boring-ui-kit@^0.3.2",
1634
+ "@hachej/boring-files@^0.3.2",
1603
1635
  "react@^19.0.0",
1604
1636
  "react-dom@^19.0.0",
1605
1637
  "lucide-react@^1.51.0"
@@ -2382,8 +2414,8 @@
2382
2414
  }
2383
2415
  },
2384
2416
  "registryDependencies": [
2385
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/pi-chat.json",
2386
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json"
2417
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-chat.json",
2418
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json"
2387
2419
  ]
2388
2420
  },
2389
2421
  {
@@ -2392,7 +2424,7 @@
2392
2424
  "title": "Feedback preview",
2393
2425
  "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
2426
  "dependencies": [
2395
- "@hachej/boring-feedback@^0.3.0",
2427
+ "@hachej/boring-feedback@^0.3.2",
2396
2428
  "@earendil-works/pi-ai@~1.2.0",
2397
2429
  "react@^19.0.0"
2398
2430
  ],
@@ -2410,8 +2442,8 @@
2410
2442
  "title": "Consumer task-list viewer",
2411
2443
  "description": "A consumer-defined task-list schema and controller over the shared text buffer, with a restylable borrowed React renderer.",
2412
2444
  "dependencies": [
2413
- "@hachej/boring-ui-kit@^0.3.0",
2414
- "@hachej/boring-files@^0.3.0",
2445
+ "@hachej/boring-ui-kit@^0.3.2",
2446
+ "@hachej/boring-files@^0.3.2",
2415
2447
  "react@^19.0.0",
2416
2448
  "react-dom@^19.0.0",
2417
2449
  "zod@^4.6.5"
@@ -2477,14 +2509,14 @@
2477
2509
  "title": "File tree",
2478
2510
  "description": "Browse, search, upload and inspect retained versions through one revisionProvider binding.",
2479
2511
  "dependencies": [
2480
- "@hachej/boring-ui-kit@^0.3.0",
2481
- "@hachej/boring-files@^0.3.0",
2512
+ "@hachej/boring-ui-kit@^0.3.2",
2513
+ "@hachej/boring-files@^0.3.2",
2482
2514
  "react@^19.0.0",
2483
2515
  "radix-ui@^1.8.0",
2484
2516
  "lucide-react@^1.51.0"
2485
2517
  ],
2486
2518
  "registryDependencies": [
2487
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/theme.json"
2519
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json"
2488
2520
  ],
2489
2521
  "files": [
2490
2522
  {
@@ -2494,6 +2526,56 @@
2494
2526
  }
2495
2527
  ],
2496
2528
  "css": {}
2529
+ },
2530
+ {
2531
+ "name": "documents",
2532
+ "type": "registry:block",
2533
+ "title": "Documents: the Library, a file and its revisions",
2534
+ "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.",
2535
+ "dependencies": [
2536
+ "@hachej/boring-ui-kit@^0.3.2",
2537
+ "@hachej/boring-files@^0.3.2",
2538
+ "react@^19.0.0",
2539
+ "lucide-react@^1.51.0"
2540
+ ],
2541
+ "registryDependencies": [
2542
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
2543
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
2544
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/button.json",
2545
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-chat.json",
2546
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/pi-app.json",
2547
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/app-shell.json",
2548
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/file-tree.json",
2549
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/viewers.json",
2550
+ "drawer"
2551
+ ],
2552
+ "files": [
2553
+ {
2554
+ "path": "registry/documents/documents.tsx",
2555
+ "type": "registry:component",
2556
+ "target": "components/documents/documents.tsx"
2557
+ },
2558
+ {
2559
+ "path": "registry/documents/document-sheet.tsx",
2560
+ "type": "registry:component",
2561
+ "target": "components/documents/document-sheet.tsx"
2562
+ },
2563
+ {
2564
+ "path": "registry/documents/revisions.tsx",
2565
+ "type": "registry:component",
2566
+ "target": "components/documents/revisions.tsx"
2567
+ },
2568
+ {
2569
+ "path": "registry/documents/words.tsx",
2570
+ "type": "registry:component",
2571
+ "target": "components/documents/words.tsx"
2572
+ },
2573
+ {
2574
+ "path": "registry/documents/documents-labels.ts",
2575
+ "type": "registry:lib",
2576
+ "target": "components/documents/documents-labels.ts"
2577
+ }
2578
+ ]
2497
2579
  }
2498
2580
  ]
2499
2581
  }
@@ -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.3.0",
8
- "@hachej/boring-files@^0.3.0",
7
+ "@hachej/boring-ui-kit@^0.3.2",
8
+ "@hachej/boring-files@^0.3.2",
9
9
  "react@^19.0.0",
10
10
  "react-dom@^19.0.0",
11
11
  "zod@^4.6.5"
package/r/viewers.json CHANGED
@@ -4,8 +4,8 @@
4
4
  "title": "Viewers",
5
5
  "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.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.3.0",
8
- "@hachej/boring-files@^0.3.0",
7
+ "@hachej/boring-ui-kit@^0.3.2",
8
+ "@hachej/boring-files@^0.3.2",
9
9
  "react@^19.0.0",
10
10
  "react-dom@^19.0.0",
11
11
  "marked@^17.0.6",
@@ -24,9 +24,9 @@
24
24
  "lucide-react@^1.51.0"
25
25
  ],
26
26
  "registryDependencies": [
27
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/theme.json",
28
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/utils.json",
29
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.0/r/button.json"
27
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/theme.json",
28
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/utils.json",
29
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.3.2/r/button.json"
30
30
  ],
31
31
  "files": [
32
32
  {