@hachej/boring-ui-kit 0.4.48 → 0.4.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/boring-ui-blocks.mjs +33 -1
- package/dist/file-tree-view.d.ts +25 -2
- package/dist/file-tree-view.js +8 -4
- package/package.json +2 -2
- package/r/agent-target.json +4 -4
- package/r/app-shell.json +12 -12
- package/r/board-viewer.json +6 -6
- package/r/button.json +1 -1
- package/r/dock.json +1 -1
- package/r/documents.json +12 -12
- package/r/feedback.json +6 -6
- package/r/file-tree.json +5 -5
- package/r/highlights.json +3 -3
- package/r/html-viewer.json +3 -3
- package/r/looks-viewer.json +2 -2
- package/r/maquette-viewer.json +1 -1
- package/r/markdown-editor.json +3 -3
- package/r/mockup-viewer.json +1 -1
- package/r/pi-ambient.json +9 -9
- package/r/pi-app-kinds.json +10 -10
- package/r/pi-app.json +10 -10
- package/r/pi-artifacts.json +2 -2
- package/r/pi-chat-approval.json +2 -2
- package/r/pi-chat-attachments.json +1 -1
- package/r/pi-chat-markdown.json +2 -2
- package/r/pi-chat-mentions.json +1 -1
- package/r/pi-chat-pickers.json +1 -1
- package/r/pi-chat-plugins.json +11 -11
- package/r/pi-chat-question.json +1 -1
- package/r/pi-chat-slash.json +2 -2
- package/r/pi-chat-subagent.json +1 -1
- package/r/pi-chat-voice.json +3 -3
- package/r/pi-chat.json +9 -9
- package/r/pi-workspace.json +3 -3
- package/r/placed-agent.json +4 -4
- package/r/provider-setup.json +1 -1
- package/r/registry.json +161 -161
- package/r/shared-chat.json +6 -6
- package/r/spaces.json +1 -1
- package/r/task-list-viewer.json +2 -2
- package/r/version-history.json +2 -2
- package/r/viewer-code.json +2 -2
- package/r/viewer-frame.json +4 -4
- package/r/viewer-html.json +4 -4
- package/r/viewer-image.json +2 -2
- package/r/viewer-markdown.json +5 -5
- package/r/viewer-pdf.json +3 -3
- package/r/viewers.json +6 -6
- package/r/windows.json +1 -1
- package/styles/file-tree.css +4 -0
package/bin/boring-ui-blocks.mjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// `check` writes nothing. `add`, `update` and `manifest` write the shadcn files they install and the manifest, nothing else.
|
|
12
12
|
import { createHash } from 'node:crypto';
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
14
14
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
15
15
|
import { spawnSync } from 'node:child_process';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
@@ -307,6 +307,37 @@ function shadcnAdd(app, args, log, overwrite) {
|
|
|
307
307
|
return { status: result.status ?? 1, skipped: skipped.map(([path]) => path), created };
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
/**
|
|
311
|
+
* A package manager follows a symlinked `node_modules` or scope directory and writes into the tree it points at: another checkout's install.
|
|
312
|
+
* Refused before anything runs. Per-package links (workspaces, pnpm) stay valid, and `check` never gets here: it writes nothing.
|
|
313
|
+
*/
|
|
314
|
+
function refuseBorrowedInstall(app) {
|
|
315
|
+
const borrowed = [];
|
|
316
|
+
for (const dir of installRoots(app)) {
|
|
317
|
+
const modules = join(dir, 'node_modules');
|
|
318
|
+
const stat = lstatSync(modules, { throwIfNoEntry: false });
|
|
319
|
+
if (stat?.isSymbolicLink()) borrowed.push(modules);
|
|
320
|
+
else if (stat) borrowed.push(...readdirSync(modules).filter(name => name.startsWith('@')).map(name => join(modules, name)).filter(path => lstatSync(path).isSymbolicLink()));
|
|
321
|
+
}
|
|
322
|
+
if (borrowed.length) throw new Error(`refusing to install through a borrowed install: ${borrowed.map(path => `${path} is a symlink`).join(', ')}. A package manager would write into the tree it points at. A borrowed install is read-only: \`check\` still works, and \`add\` and \`update\` need an owned physical install.`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Where a package manager run from `app` may write: the app, and every directory up to its workspace root when it is in a workspace. */
|
|
326
|
+
function installRoots(app) {
|
|
327
|
+
const walked = [];
|
|
328
|
+
for (let dir = app; ; dir = dirname(dir)) {
|
|
329
|
+
walked.push(dir);
|
|
330
|
+
if (isWorkspaceRoot(dir)) return walked;
|
|
331
|
+
if (dirname(dir) === dir) return [app];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function isWorkspaceRoot(dir) {
|
|
336
|
+
if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return true;
|
|
337
|
+
try { return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).workspaces !== undefined; }
|
|
338
|
+
catch { return false; }
|
|
339
|
+
}
|
|
340
|
+
|
|
310
341
|
const line = v => `${v.check} ${v.rule} ${v.file}${v.line ? `:${v.line}:${v.column}` : ''} ${v.detail}`;
|
|
311
342
|
|
|
312
343
|
export function main(argv, cwd = process.cwd(), log = console.log, error = console.error) {
|
|
@@ -328,6 +359,7 @@ export function main(argv, cwd = process.cwd(), log = console.log, error = conso
|
|
|
328
359
|
return 1;
|
|
329
360
|
}
|
|
330
361
|
if (command === 'manifest') { log(`${flags.manifest ?? MANIFEST}: ${writeManifest(app, flags.manifest)} file(s)`); return 0; }
|
|
362
|
+
if (command === 'add' || (command === 'update' && !flags.dryRun)) refuseBorrowedInstall(app);
|
|
331
363
|
if (command === 'add') {
|
|
332
364
|
appPaths(app);
|
|
333
365
|
if (!flags.pass.length) throw new Error('add needs shadcn add arguments');
|
package/dist/file-tree-view.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Collapsible } from '@base-ui/react/collapsible';
|
|
2
2
|
import type { ComponentProps, ReactNode } from 'react';
|
|
3
|
-
import type { RevisionProvider, SavedRevision } from '@hachej/boring-files/revision';
|
|
3
|
+
import type { FileEntry, RevisionProvider, SavedRevision } from '@hachej/boring-files/revision';
|
|
4
4
|
import type { FileTreeController } from './file-tree.js';
|
|
5
5
|
/** Every word of the tree, so a host can translate it. Functions receive what they name. */
|
|
6
6
|
export declare const defaultFileTreeLabels: {
|
|
@@ -109,6 +109,16 @@ export interface FileTreeDialogProps {
|
|
|
109
109
|
readonly footer: ReactNode;
|
|
110
110
|
}
|
|
111
111
|
/** Host presentation. The registry supplies real shadcn Base UI controls and shared version rows; no primitive fallback. */
|
|
112
|
+
/** A row's status badge, as `decorate` gave it. */
|
|
113
|
+
export interface FileTreeBadgeProps {
|
|
114
|
+
readonly tone: FileDecorationTone;
|
|
115
|
+
readonly children: ReactNode;
|
|
116
|
+
readonly role: 'note';
|
|
117
|
+
readonly 'aria-label': string;
|
|
118
|
+
readonly title: string;
|
|
119
|
+
readonly 'data-file-badge': '';
|
|
120
|
+
readonly 'data-tone': FileDecorationTone;
|
|
121
|
+
}
|
|
112
122
|
export interface FileTreeComponents {
|
|
113
123
|
readonly Button: (props: FileTreeButtonProps) => ReactNode;
|
|
114
124
|
readonly Actions: (props: FileTreeActionsProps) => ReactNode;
|
|
@@ -138,6 +148,8 @@ export interface FileTreeComponents {
|
|
|
138
148
|
readonly value: number | null;
|
|
139
149
|
readonly 'aria-label': string;
|
|
140
150
|
}) => ReactNode;
|
|
151
|
+
/** The host's shadcn `Badge` for a row's status (`decorate`); without it the status is plain text. */
|
|
152
|
+
readonly Badge?: (props: FileTreeBadgeProps) => ReactNode;
|
|
141
153
|
/** The host's shadcn `Dialog`. Without it the tree offers neither Delete nor Move to… (dragging a file onto a folder still moves it). */
|
|
142
154
|
readonly Dialog?: (props: FileTreeDialogProps) => ReactNode;
|
|
143
155
|
}
|
|
@@ -159,6 +171,15 @@ export type UploadBlob = (file: globalThis.File, options: {
|
|
|
159
171
|
readonly signal: AbortSignal;
|
|
160
172
|
readonly onProgress: (progress: UploadProgress) => void;
|
|
161
173
|
}) => Promise<unknown>;
|
|
174
|
+
export type FileDecorationTone = 'neutral' | 'success' | 'warning' | 'danger';
|
|
175
|
+
/** A row's status from the host (D-1374): a badge, read with the file's name, and a short secondary text. */
|
|
176
|
+
export interface FileDecoration {
|
|
177
|
+
readonly badge?: {
|
|
178
|
+
readonly label: string;
|
|
179
|
+
readonly tone: FileDecorationTone;
|
|
180
|
+
};
|
|
181
|
+
readonly detail?: string;
|
|
182
|
+
}
|
|
162
183
|
export interface FileTreeProps {
|
|
163
184
|
readonly components: FileTreeComponents;
|
|
164
185
|
readonly revisionProvider: RevisionProvider;
|
|
@@ -201,6 +222,8 @@ export interface FileTreeProps {
|
|
|
201
222
|
readonly onRenamed?: (from: string, to: string) => void;
|
|
202
223
|
/** A delete from the tree committed (a host showing `path` closes it). */
|
|
203
224
|
readonly onDeleted?: (path: string) => void;
|
|
225
|
+
/** The host's status for a row (for example « Lu · 12 lignes »); nothing, or no prop, leaves the row as it is. */
|
|
226
|
+
readonly decorate?: (entry: FileEntry) => FileDecoration | null | undefined;
|
|
204
227
|
}
|
|
205
228
|
/** One borrowed binding powers browsing, search, upload and history. Historical previews never replace an editor's draft. */
|
|
206
229
|
export declare function FileTree(props: FileTreeProps): import("react").JSX.Element;
|
|
@@ -208,5 +231,5 @@ export interface FileTreeViewProps extends Omit<FileTreeProps, 'revisionProvider
|
|
|
208
231
|
readonly controller: FileTreeController;
|
|
209
232
|
}
|
|
210
233
|
/** Renders a borrowed controller, for a workspace whose chat and tree share upload state. */
|
|
211
|
-
export declare function FileTreeView({ components, controller, onOpen, selectedPath, className, uploadDirectory, locale, labels: given, upload, history, refresh, variant, title, onClose, refreshKey, historyKey, maxBytes, tooLarge, uploadBlob, capabilities, onRenamed, onDeleted }: FileTreeViewProps): import("react").JSX.Element;
|
|
234
|
+
export declare function FileTreeView({ components, controller, onOpen, selectedPath, className, uploadDirectory, locale, labels: given, upload, history, refresh, variant, title, onClose, refreshKey, historyKey, maxBytes, tooLarge, uploadBlob, capabilities, onRenamed, onDeleted, decorate }: FileTreeViewProps): import("react").JSX.Element;
|
|
212
235
|
//# sourceMappingURL=file-tree-view.d.ts.map
|
package/dist/file-tree-view.js
CHANGED
|
@@ -171,7 +171,7 @@ function BoundFileTree(props) {
|
|
|
171
171
|
return owned?.provider === props.revisionProvider ? _jsx(FileTreeView, { ...props, controller: owned.controller }) : _jsx("p", { role: "status", children: props.labels?.loading ?? LOCALES[props.locale ?? 'en'].loading });
|
|
172
172
|
}
|
|
173
173
|
/** Renders a borrowed controller, for a workspace whose chat and tree share upload state. */
|
|
174
|
-
export function FileTreeView({ components, controller, onOpen, selectedPath, className, uploadDirectory = 'uploads', locale = 'en', labels: given, upload = true, history = true, refresh = true, variant = 'list', title, onClose, refreshKey, historyKey, maxBytes, tooLarge, uploadBlob, capabilities, onRenamed, onDeleted }) {
|
|
174
|
+
export function FileTreeView({ components, controller, onOpen, selectedPath, className, uploadDirectory = 'uploads', locale = 'en', labels: given, upload = true, history = true, refresh = true, variant = 'list', title, onClose, refreshKey, historyKey, maxBytes, tooLarge, uploadBlob, capabilities, onRenamed, onDeleted, decorate }) {
|
|
175
175
|
const changed = useRef({ onRenamed, onDeleted });
|
|
176
176
|
changed.current = { onRenamed, onDeleted };
|
|
177
177
|
/** Tell the host about a committed rename or delete, so a viewer showing the file follows or closes. */
|
|
@@ -184,7 +184,7 @@ export function FileTreeView({ components, controller, onOpen, selectedPath, cla
|
|
|
184
184
|
changed.current.onDeleted?.(mutation.path);
|
|
185
185
|
return mutation;
|
|
186
186
|
};
|
|
187
|
-
const { Button, Actions, Menu: SidebarMenu, MenuItem: SidebarMenuItem, MenuSub: SidebarMenuSub, Input, Collapsible: CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, Empty, Progress, Dialog } = components;
|
|
187
|
+
const { Button, Actions, Menu: SidebarMenu, MenuItem: SidebarMenuItem, MenuSub: SidebarMenuSub, Input, Collapsible: CollapsibleRoot, CollapsibleTrigger, CollapsibleContent, Empty, Progress, Dialog, Badge } = components;
|
|
188
188
|
const can = {
|
|
189
189
|
// The client's methods turn the actions on; capabilities, when the host passes them, only narrow.
|
|
190
190
|
rename: controller.actions.rename && (capabilities === undefined || capabilities.effective.includes('rename')),
|
|
@@ -471,8 +471,9 @@ export function FileTreeView({ components, controller, onOpen, selectedPath, cla
|
|
|
471
471
|
const mutation = mutationRows.get(entry.path);
|
|
472
472
|
const changeable = mutable(entry);
|
|
473
473
|
const draggable = changeable && can.rename && !folder;
|
|
474
|
+
const decoration = decorate?.(entry) ?? undefined;
|
|
474
475
|
const button = _jsxs(Button, { type: "button", variant: selectedPath === entry.path ? 'secondary' : 'ghost', "data-active": selectedPath === entry.path, tabIndex: -1, title: entry.path, disabled: temporary, className: "file-tree-row-button", draggable: draggable || undefined, onClick: () => { focus(entry.path); if (!folder)
|
|
475
|
-
onOpen?.(entry.path); }, children: [folder ? _jsx(ChevronRight, { "aria-hidden": "true" }) : list && _jsx("span", { className: "file-tree-chevron-space", "aria-hidden": "true" }), _jsx(Icon, { "aria-hidden": "true" }), _jsxs("span", { className: "file-tree-copy", children: [_jsx("span", { className: "file-tree-name", children: name }), list ? folder ? childCount !== undefined && _jsx("span", { "data-file-meta": true, children: childCount }) : _jsxs("span", { "data-file-meta": true, children: [kind.label, " \u00B7 ", fileSize(blob?.size ?? entry.size, locale), parent && ` · ${parent}`] }) : parent && _jsx("span", { className: "file-tree-parent", "data-file-meta": true, children: parent })] })] });
|
|
476
|
+
onOpen?.(entry.path); }, children: [folder ? _jsx(ChevronRight, { "aria-hidden": "true" }) : list && _jsx("span", { className: "file-tree-chevron-space", "aria-hidden": "true" }), _jsx(Icon, { "aria-hidden": "true" }), _jsxs("span", { className: "file-tree-copy", children: [_jsx("span", { className: "file-tree-name", children: name }), list ? folder ? childCount !== undefined && _jsx("span", { "data-file-meta": true, children: childCount }) : _jsxs("span", { "data-file-meta": true, children: [kind.label, " \u00B7 ", fileSize(blob?.size ?? entry.size, locale), parent && ` · ${parent}`] }) : parent && _jsx("span", { className: "file-tree-parent", "data-file-meta": true, children: parent }), decoration?.detail && _jsx("span", { "data-file-detail": true, children: decoration.detail })] })] });
|
|
476
477
|
const editing = renaming?.path === entry.path && can.rename;
|
|
477
478
|
const mutationFailed = mutation?.state.kind === 'settled';
|
|
478
479
|
const item = _jsxs(SidebarMenuItem, { role: "treeitem", "data-path": entry.path, "data-blob-type": blob?.type, "data-upload-state": status?.kind, "data-mutation-state": mutation ? mutationFailed ? 'failed' : 'pending' : undefined, "data-drop-target": folder && dropTarget === entry.path ? 'true' : undefined, "data-dragging": dragged === entry.path || undefined, "aria-busy": status?.kind === 'pending' || mutation?.state.kind === 'pending' || undefined, ...(folder ? dropHandlers(entry.path) : {}), ...(draggable ? {
|
|
@@ -503,7 +504,10 @@ export function FileTreeView({ components, controller, onOpen, selectedPath, cla
|
|
|
503
504
|
},
|
|
504
505
|
// Focus moved somewhere on purpose cancels; a closing menu that drops focus (no related target) does not.
|
|
505
506
|
onBlur: event => { if (event.relatedTarget)
|
|
506
|
-
setRenaming(current => current?.path === entry.path ? undefined : current); } })] }) : folder ? _jsx(CollapsibleTrigger, { render: button }) : button,
|
|
507
|
+
setRenaming(current => current?.path === entry.path ? undefined : current); } })] }) : folder ? _jsx(CollapsibleTrigger, { render: button }) : button, decoration?.badge && !editing && (() => {
|
|
508
|
+
const badge = { tone: decoration.badge.tone, children: decoration.badge.label, role: 'note', 'aria-label': `${name}: ${decoration.badge.label}`, title: decoration.badge.label, 'data-file-badge': '', 'data-tone': decoration.badge.tone };
|
|
509
|
+
return Badge ? _jsx(Badge, { ...badge }) : _jsx("span", { role: badge.role, "aria-label": badge['aria-label'], title: badge.title, "data-file-badge": "", "data-tone": badge.tone, children: badge.children });
|
|
510
|
+
})(), (status?.kind === 'pending' || mutation?.state.kind === 'pending') && _jsx(LoaderCircle, { "data-upload-spinner": status ? '' : undefined, "data-mutation-spinner": mutation ? '' : undefined, className: "animate-spin motion-reduce:animate-none", "aria-hidden": "true" }), status?.kind === 'failed' && _jsx(Button, { type: "button", variant: "ghost", "data-upload-retry": true, "aria-label": status.retryLabel, onClick: status.retry, children: status.retryLabel }), mutation && mutationFailed && _jsx(Button, { type: "button", variant: "ghost", size: "icon", "data-mutation-dismiss": true, "aria-label": labels.dismiss(entry.path), title: labels.dismiss(entry.path), onClick: () => controller.dismiss(mutation.operationId), children: _jsx(X, { "aria-hidden": "true" }) }), !temporary && !status && !editing && mutation?.state.kind !== 'pending' && (!folder || changeable && (can.rename || can.delete)) && _jsx(FileActions, { Actions: Actions, controller: controller, entry: entry, labels: labels, onOpen: onOpen && !folder ? () => onOpen(entry.path) : undefined, onHistory: history && !folder ? () => setHistoryPath(entry.path) : undefined, onError: setUploadError, onRename: changeable && can.rename && renameable(entry) ? () => startRename(entry.path) : undefined, onMove: changeable && can.rename && Dialog ? () => ask('move', entry.path) : undefined, onDelete: changeable && can.delete ? () => ask('delete', entry.path) : undefined })] }), status?.kind === 'failed' && _jsx("span", { "data-upload-error": true, children: status.reason }), editing && renaming?.error && _jsx("span", { "data-mutation-error": true, role: "alert", children: renaming.error }), mutation && mutationFailed && _jsx("span", { "data-mutation-error": true, children: mutationReason(mutation) }), folder && !state.query && _jsx(CollapsibleContent, { render: _jsx(SidebarMenuSub, { role: "group" }), children: renderEntries(entry.path, level + 1) })] }, entry.path);
|
|
507
511
|
return folder ? _jsx(CollapsibleRoot, { render: item, open: state.expanded.has(entry.path), onOpenChange: () => { void controller.toggle(entry.path); } }, entry.path) : item;
|
|
508
512
|
};
|
|
509
513
|
const visibleListing = state.query ? state.search : state.directories.get('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-ui-kit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.49",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
}
|
|
151
151
|
},
|
|
152
152
|
"peerDependencies": {
|
|
153
|
-
"@hachej/boring-files": "^0.4.
|
|
153
|
+
"@hachej/boring-files": "^0.4.49",
|
|
154
154
|
"@earendil-works/pi-durable": "~1.4.0",
|
|
155
155
|
"react": "^19.0.0",
|
|
156
156
|
"react-dom": "^19.0.0",
|
package/r/agent-target.json
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
"title": "Point and dictate on app data",
|
|
5
5
|
"description": "Tap explicitly marked JSON app context, dictate once and show the same app agent’s native reply without opening chat. Host-controlled presentation; no feedback report or second controller.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
8
|
-
"@hachej/boring-feedback@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.49",
|
|
8
|
+
"@hachej/boring-feedback@^0.4.49",
|
|
9
9
|
"@earendil-works/pi-durable@~1.4.0",
|
|
10
10
|
"react@^19.0.0",
|
|
11
11
|
"react-dom@^19.0.0"
|
|
12
12
|
],
|
|
13
13
|
"registryDependencies": [
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.49/r/app-shell.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.49/r/pi-chat.json"
|
|
16
16
|
],
|
|
17
17
|
"files": [
|
|
18
18
|
{
|