@hachej/boring-ui-kit 0.4.29 → 0.4.31
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/README.md +6 -0
- package/dist/chat-artifacts.d.ts +36 -19
- package/dist/chat-artifacts.js +48 -30
- package/dist/chat-react.d.ts +15 -17
- package/dist/chat-react.js +7 -6
- package/dist/task-artifacts.d.ts +22 -0
- package/dist/task-artifacts.js +34 -0
- package/dist/task-board.d.ts +83 -0
- package/dist/task-board.js +42 -0
- package/package.json +6 -2
- package/r/agent-target.json +4 -4
- package/r/app-shell.json +15 -14
- 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-preview.json +1 -1
- package/r/feedback.json +7 -7
- package/r/file-tree.json +4 -4
- package/r/highlights.json +3 -3
- package/r/html-viewer.json +3 -3
- package/r/looks-viewer.json +2 -2
- package/r/maquette-viewer.json +1 -1
- package/r/markdown-editor.json +3 -3
- package/r/mockup-viewer.json +2 -2
- package/r/pi-ambient.json +9 -9
- package/r/pi-app-kinds.json +10 -10
- package/r/pi-app.json +12 -12
- package/r/pi-artifacts.json +4 -4
- 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 +2 -2
- 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 +10 -10
- package/r/pi-workspace.json +3 -3
- package/r/placed-agent.json +4 -4
- package/r/pm-panel.json +98 -0
- package/r/provider-setup.json +1 -1
- package/r/registry.json +391 -166
- package/r/requests-board.json +1 -1
- package/r/shared-chat.json +6 -6
- package/r/task-board.json +35 -0
- package/r/task-list-viewer.json +2 -2
- package/r/task-mentions.json +25 -0
- package/r/theme.json +88 -3
- 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/r/requests-board.json
CHANGED
package/r/shared-chat.json
CHANGED
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
"title": "Shared chat",
|
|
5
5
|
"description": "Optional multi-party host log presentation over PiChat's generic composition seams. No agent runtime or speaking policy.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.31",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0"
|
|
10
10
|
],
|
|
11
11
|
"registryDependencies": [
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
16
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/pi-chat.json",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/pi-chat-plugins.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/pi-chat-markdown.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/pi-chat-mentions.json",
|
|
16
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/utils.json"
|
|
17
17
|
],
|
|
18
18
|
"files": [
|
|
19
19
|
{
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "task-board",
|
|
4
|
+
"title": "Task board",
|
|
5
|
+
"description": "Host-owned business requests: board, stacked task/file overlays and compact own-request rows. No agent or task backend required.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.31",
|
|
8
|
+
"react@^19.0.0",
|
|
9
|
+
"lucide-react@^1.51.0"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/utils.json"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "registry/task-board/task-board.tsx",
|
|
17
|
+
"content": "'use client';\n\nimport { useEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { MouseEvent, ReactNode } from 'react';\nimport { selectTasks, taskFromUrl, taskReference, taskUrl, TASK_STATES } from '@hachej/boring-ui-kit/task-board';\nimport type { TaskFile, TaskFilter, TaskRecord, TaskState } from '@hachej/boring-ui-kit/task-board';\nimport { ChevronLeftIcon } from 'lucide-react';\nimport { Badge } from '@/components/ui/badge';\nimport { Button } from '@/components/ui/button';\nimport { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty';\nimport { Separator } from '@/components/ui/separator';\nimport { cn } from '../utils/utils';\nimport { TaskRequestCard, TaskStatusPill, taskMeta, taskStateLabels, taskTokens } from './task-request-card';\nimport type { TaskLocale, TaskViewerAction } from './task-request-card';\nimport { TaskDetail } from './task-detail';\nimport type { TaskDetailProps } from './task-detail';\n\nexport { TaskRequestCard, TaskRequestCard as TaskCard, TaskStatusPill, TaskStatusChip, TaskAttention, taskMeta, taskTokens } from './task-request-card';\nexport { TaskDetail } from './task-detail';\nexport type { TaskRequestCardProps, TaskRequestCardProps as TaskCardProps, TaskLocale, TaskViewerAction } from './task-request-card';\nexport type { TaskDetailProps } from './task-detail';\nexport type { TaskFile, TaskRecord, TaskFilter, TaskState } from '@hachej/boring-ui-kit/task-board';\n\nconst navigation = 'boring:task-navigation';\nconst subscribe = (changed: () => void) => {\n window.addEventListener('popstate', changed); window.addEventListener(navigation, changed);\n return () => { window.removeEventListener('popstate', changed); window.removeEventListener(navigation, changed); };\n};\nconst readSelection = () => taskFromUrl(window.location.href);\nconst serverSelection = () => null;\nconst BOARD_STATES = TASK_STATES.slice(0, 3);\ntype TaskList = 'open' | 'done' | 'closed';\n\nexport interface TaskFileViewApi {\n readonly fullscreen: boolean;\n readonly onFullscreenChange: (fullscreen: boolean) => void;\n readonly onClose: () => void;\n readonly onBack: () => void;\n /** The host's PM pointing service; absent unless onShowFile was supplied. */\n readonly onShowFile?: (() => void) | undefined;\n}\nexport interface TaskBoardProps {\n readonly tasks: readonly TaskRecord[];\n /** Controlled Mine/All. Uncontrolled, the tasks bar starts on Mine when `currentUser` is known, otherwise All. */\n readonly filter?: TaskFilter;\n readonly onFilterChange?: ((filter: TaskFilter) => void) | undefined;\n readonly currentUser?: string;\n readonly limit?: number;\n readonly variant?: 'board' | 'compact';\n readonly title?: string;\n /** Column names, shown uppercase. */\n readonly labels?: Readonly<Record<TaskState, string>>;\n /** The tasks bar (« 5 open · Done 12 · Closed 1 », Mine/All). Hosts that draw it in their own header pass false. */\n readonly bar?: boolean;\n /** Host content at the start of the tasks bar, e.g. the collapsed Studio header's app name and « Show the app ». */\n readonly barStart?: ReactNode;\n readonly locale?: TaskLocale;\n /** Explicit host context for compact French labels (« À vous » or « À confirmer »). */\n readonly viewerAction?: TaskViewerAction;\n /** Omit to use ?task= with native history. A router may instead supply controlled selection. */\n readonly selectedTask?: string | null;\n readonly onSelectedTaskChange?: ((id: string | null) => void) | undefined;\n /** Optional host navigation/present path, particularly for compact lists inside the PM panel. */\n readonly onOpen?: ((task: TaskRecord) => void) | undefined;\n /** Host content under the detail's title card. */\n readonly actions?: ((task: TaskRecord) => ReactNode) | undefined;\n /** Host-authorized compact card actions (« Modifier », « Confirmer »). */\n readonly cardActions?: ((task: TaskRecord) => ReactNode) | undefined;\n readonly onAnswer?: TaskDetailProps['onAnswer'];\n readonly onOther?: TaskDetailProps['onOther'];\n readonly onDiscussQuestion?: TaskDetailProps['onDiscussQuestion'];\n readonly onAsk?: TaskDetailProps['onAsk'];\n /** Adapter paths only. The host reauthorizes them when opening the regular viewer. */\n readonly onOpenFile?: TaskDetailProps['onOpenFile'];\n readonly onShowFile?: TaskDetailProps['onOpenFile'];\n /** Supply the host's regular FileViewer; it opens 760px wide over the task detail. No second store or composer. */\n readonly renderFile?: ((task: TaskRecord, file: TaskFile, api: TaskFileViewApi) => ReactNode) | undefined;\n /** Host-owned overlay appearance (for example the host's overlay shadow token). */\n readonly overlayClassName?: string;\n readonly className?: string;\n}\n\n/**\n * The tasks panel (spec 3 §3–§5): tasks bar, WAITING · WORKING · OPEN, and panel-scoped overlays. The task detail opens 400px wide\n * from the right over the board, which keeps its columns; a file opens 760px wide over the detail. `variant=\"compact\"` stacks C7 cards.\n */\nexport function TaskBoard({ tasks, filter, onFilterChange, currentUser, limit, variant = 'board', title = 'Tasks', labels = taskStateLabels, bar = true, barStart, locale = 'en', viewerAction,\n selectedTask, onSelectedTaskChange, onOpen, actions, cardActions, onAnswer, onOther, onDiscussQuestion, onAsk, onOpenFile, onShowFile, renderFile, overlayClassName, className }: TaskBoardProps) {\n const urlSelection = useSyncExternalStore(subscribe, readSelection, serverSelection);\n const [ownFilter, setOwnFilter] = useState<TaskFilter>(currentUser === undefined ? 'all' : 'mine');\n const [list, setList] = useState<TaskList>('open');\n const shownFilter = filter ?? ownFilter;\n const selected = selectedTask === undefined ? onOpen ? null : urlSelection : selectedTask;\n const detail = selected === null ? undefined : tasks.find(task => task.id === selected || taskReference(task) === selected);\n const shown = selectTasks(tasks, { filter: shownFilter, ...(currentUser === undefined ? {} : { currentUser }), ...(limit === undefined ? {} : { limit }) });\n const [fileSelection, setFileSelection] = useState<{ readonly taskId: string; readonly path: string } | null>(null);\n const filePath = fileSelection?.path;\n const file = detail?.id === fileSelection?.taskId ? detail?.files?.find(candidate => candidate.path === filePath) : undefined;\n const fileOpen = !!file && !!renderFile;\n const [fileFullscreen, setFileFullscreen] = useState(false);\n const closeFile = () => { setFileSelection(null); setFileFullscreen(false); };\n const openFile = (task: TaskRecord, file: TaskFile) => { setFileSelection({ taskId: task.id, path: file.path }); onOpenFile?.(task, file); };\n const root = useRef<HTMLElement>(null);\n const back = useRef<HTMLButtonElement>(null);\n const previous = useRef<{ readonly id: string; readonly state: TaskState | undefined } | null>(null);\n const pendingFocus = useRef<string | null>(null);\n const cardLink = (id: string) => [...root.current?.querySelectorAll<HTMLAnchorElement>('[data-task-open]') ?? []].find(candidate => candidate.dataset['taskOpen'] === id);\n useEffect(() => {\n if (selected !== null) back.current?.focus();\n else if (previous.current !== null) {\n const { id, state } = previous.current, link = cardLink(id);\n if (link) link.focus();\n else if (state === 'done' || state === 'closed') { pendingFocus.current = id; setList(state); }\n }\n previous.current = selected === null ? null : { id: detail?.id ?? selected, state: detail?.state };\n }, [selected, detail?.id, detail?.state]);\n useEffect(() => { if (pendingFocus.current !== null) { cardLink(pendingFocus.current)?.focus(); pendingFocus.current = null; } }, [list]);\n const select = (id: string | null) => {\n closeFile();\n if (selectedTask === undefined) {\n window.history.pushState(null, '', taskUrl(window.location.href, id));\n window.dispatchEvent(new Event(navigation));\n }\n onSelectedTaskChange?.(id);\n };\n const open = (task: TaskRecord) => { if (onOpen) onOpen(task); else select(task.id); };\n const href = (task: TaskRecord) => typeof window === 'undefined' ? `?task=${encodeURIComponent(task.id)}` : taskUrl(window.location.href, task.id);\n const card = (task: TaskRecord) => <TaskRequestCard key={task.id} task={task} variant={variant === 'compact' ? 'compact' : 'card'} selected={task.id === detail?.id} href={href(task)} onOpen={open}\n locale={locale} viewerAction={viewerAction} actions={variant === 'compact' ? cardActions?.(task) : undefined} />;\n const of = (state: TaskState) => shown.filter(task => task.state === state);\n const empty = <Empty><EmptyHeader><EmptyTitle>No requests</EmptyTitle><EmptyDescription>No tasks match this view.</EmptyDescription></EmptyHeader></Empty>;\n const switcher = (target: TaskList, text: string) => list === target ? <span data-task-list-link={target} aria-current=\"page\">{text}</span>\n : <a href=\"#\" data-task-list-link={target} className=\"text-foreground underline underline-offset-2\" onClick={(event: MouseEvent) => { event.preventDefault(); setList(target); }}>{text}</a>;\n const chooseFilter = (next: TaskFilter) => (event: MouseEvent) => { event.preventDefault(); if (filter === undefined) setOwnFilter(next); onFilterChange?.(next); };\n if (variant === 'compact') return <section ref={root} data-boring=\"task-board\" data-variant=\"compact\" aria-label={title} className={cn('min-w-0', className)}>\n <div data-task-list role=\"list\" aria-label={title} className=\"flex flex-col gap-2\">{shown.length ? shown.map(card) : empty}</div>\n </section>;\n return <section ref={root} data-boring=\"task-board\" data-variant={variant} aria-label={title} className={cn('flex min-h-0 min-w-0 flex-col', className)} onKeyDown={event => {\n if (event.key === 'Escape' && selected !== null && !event.defaultPrevented) { event.preventDefault(); if (fileOpen) closeFile(); else select(null); }\n }}>\n {bar && <nav data-task-bar aria-label=\"Task lists\" className=\"flex h-13 shrink-0 items-center gap-2.5 px-3.5 text-[13px] text-muted-foreground\">\n {barStart}\n {switcher('open', `${BOARD_STATES.reduce((sum, state) => sum + of(state).length, 0)} open`)}<span aria-hidden=\"true\">·</span>\n {switcher('done', `Done ${of('done').length}`)}<span aria-hidden=\"true\">·</span>\n {switcher('closed', `Closed ${of('closed').length}`)}\n {currentUser !== undefined && <span className={cn('ml-auto flex items-center gap-2.5', taskTokens.pill)} data-task-filter={shownFilter}>\n {(['mine', 'all'] as const).map(value => <Badge key={value} variant={shownFilter === value ? 'default' : 'secondary'} render={<a href=\"#\" aria-current={shownFilter === value ? 'true' : undefined} onClick={chooseFilter(value)} />}>{value === 'mine' ? 'Mine' : 'All'}</Badge>)}\n </span>}\n </nav>}\n <div data-task-stage className=\"relative min-h-0 flex-1 overflow-hidden\">\n <div data-task-list className=\"@container/task-rails h-full min-w-0 overflow-auto\">\n {list === 'open' ? <div className=\"grid min-w-0 content-start gap-2.5 p-3.5 @2xl/task-rails:grid-cols-3\">\n {BOARD_STATES.map(state => {\n const column = of(state).sort((a, b) => Number(b.waiting_on_you === true) - Number(a.waiting_on_you === true));\n const onYou = column.filter(task => task.waiting_on_you === true).length;\n return <section key={state} data-task-column={state} aria-label={labels[state]} className={cn('flex min-w-0 flex-col gap-[7px] self-start bg-muted p-2', taskTokens.column)}>\n <h3 className=\"flex min-h-6 items-center justify-between gap-2 px-0.5 pb-0.5 text-[11px] font-semibold tracking-[0.06em] text-muted-foreground\"><span className=\"uppercase\">{labels[state]} {column.length}</span>{onYou > 0 && <span className={cn('contents', taskTokens.pill)}><Badge data-task-on-you=\"\">{onYou} on you</Badge></span>}</h3>\n <div role=\"list\" className=\"flex flex-col gap-[7px]\">{column.map(card)}</div>\n </section>;\n })}\n </div> : <section data-task-column={list} aria-label={labels[list]} className=\"flex max-w-xl min-w-0 flex-col gap-[7px] p-3.5\">\n <div role=\"list\" className=\"flex flex-col gap-[7px]\">{of(list).map(card)}</div>\n {!of(list).length && empty}\n </section>}\n </div>\n {selected !== null && <aside aria-label=\"Task detail\" data-task-side className={cn('absolute inset-y-0 right-0 flex w-[400px] max-w-full flex-col bg-popover text-popover-foreground', overlayClassName)} aria-live=\"polite\">\n <Separator orientation=\"vertical\" className=\"absolute inset-y-0 left-0\" />\n <div className=\"flex h-10 shrink-0 items-center gap-2 pr-1.5 pl-3.5\">\n <Button ref={back} variant=\"ghost\" size=\"icon\" data-task-back=\"\" aria-label=\"Back to the tasks\" title=\"Back to the tasks\" onClick={() => select(null)}><ChevronLeftIcon aria-hidden=\"true\" /></Button>\n {detail && <><span className=\"text-[11px] text-muted-foreground\">{taskMeta(detail)}</span><TaskStatusPill task={detail} locale={locale} viewerAction={viewerAction} /></>}\n </div>\n <Separator />\n <div className=\"min-h-0 flex-1 overflow-auto p-3.5\">\n {detail ? <TaskDetail key={detail.id} task={detail} actions={actions?.(detail)} onAnswer={onAnswer} onOther={onOther} onDiscussQuestion={onDiscussQuestion} onAsk={onAsk} onOpenFile={renderFile || onOpenFile ? openFile : undefined} />\n : <Empty><EmptyHeader><EmptyTitle>Task unavailable</EmptyTitle><EmptyDescription>The host has not supplied this task.</EmptyDescription></EmptyHeader></Empty>}\n </div>\n </aside>}\n {fileOpen && detail && file && <section aria-label=\"Task file viewer\" data-task-file-pane={detail.id} className={cn('flex min-w-0 flex-col bg-background', fileFullscreen ? 'fixed inset-0 z-50' : 'absolute inset-y-0 right-0 w-[760px] max-w-full', overlayClassName)}>\n {renderFile!(detail, file, { fullscreen: fileFullscreen, onFullscreenChange: setFileFullscreen, onClose: closeFile, onBack: closeFile, onShowFile: onShowFile ? () => onShowFile(detail, file) : undefined })}\n </section>}\n </div>\n </section>;\n}\n",
|
|
18
|
+
"type": "registry:component",
|
|
19
|
+
"target": "components/task-board/task-board.tsx"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "registry/task-board/task-request-card.tsx",
|
|
23
|
+
"content": "'use client';\n\nimport type { MouseEvent, ReactNode } from 'react';\nimport type { TaskRecord, TaskState } from '@hachej/boring-ui-kit/task-board';\nimport { deriveTaskStatus, taskReference, taskStatusLabel } from '@hachej/boring-ui-kit/task-board';\nimport { Badge } from '@/components/ui/badge';\nimport { Item } from '@/components/ui/item';\nimport { cn } from '../utils/utils';\n\nexport type TaskLocale = 'en' | 'fr';\nexport type TaskViewerAction = 'question' | 'confirmation';\nexport const taskStateLabels: Readonly<Record<TaskState, string>> = { waiting: 'Waiting', working: 'Working', open: 'Open', done: 'Done', closed: 'Closed' };\nconst captures = (count: number) => `${count} capture${count === 1 ? '' : 's'}`;\n/** Compact (C7) strings. French uses a non-breaking space before the colon. */\nconst compactStrings = {\n en: { label: 'REQUEST', screen: (screen: string) => `Screen: ${screen}` },\n fr: { label: 'DEMANDE', screen: (screen: string) => `Écran : ${screen}` },\n} as const;\n\n/**\n * Task-board tokens, set by the host theme. Each falls back to the stock shadcn value, so a host without them looks unchanged.\n * `--task-pill-height` / `--task-pill-font-size` (pills), `--task-control-height` (question controls), `--task-column-radius`,\n * `--task-card-padding-y` / `--task-card-padding-x` (board cards).\n */\nexport const taskTokens = {\n pill: '*:h-[var(--task-pill-height,1.25rem)] *:text-[length:var(--task-pill-font-size,0.75rem)]',\n control: '*:h-[var(--task-control-height,2rem)]',\n card: '[&_[data-slot=item]]:px-[var(--task-card-padding-x,0.75rem)] [&_[data-slot=item]]:py-[var(--task-card-padding-y,0.625rem)]',\n column: 'rounded-[var(--task-column-radius,var(--radius-xl))]',\n} as const;\n\n/** `id · age`, e.g. « D-201 · 12m ». */\nexport const taskMeta = (task: TaskRecord) => [taskReference(task), task.age].filter(Boolean).join(' · ');\n\n/**\n * One pill per task: viewer, explicit builder, then high risk (`deriveTaskStatus`); none renders nothing. Colours are theme roles:\n * primary (waiting on you), secondary (builder), destructive (risk). The French app's « À confirmer / À vous » pill uses the host's\n * warning-soft tokens when it defines them.\n */\nexport function TaskStatusPill({ task, locale = 'en', viewerAction = 'question' }: { readonly task: Pick<TaskRecord, 'waiting_on_you' | 'waiting_on' | 'risk'>; readonly locale?: TaskLocale | undefined; readonly viewerAction?: TaskViewerAction | undefined }) {\n const status = deriveTaskStatus(task);\n const label = taskStatusLabel(status, locale, viewerAction);\n if (!label) return null;\n const warning = status === 'waiting-on-viewer' && locale === 'fr';\n const badge = <Badge data-task-status={status} variant={status === 'waiting-on-viewer' && !warning ? 'default' : status === 'high-risk' ? 'destructive' : 'secondary'}>{label}</Badge>;\n return <span className={cn('contents', taskTokens.pill, warning && '[--secondary:var(--warning-soft,var(--secondary))] [--secondary-foreground:var(--warning-soft-foreground,var(--secondary-foreground))]')}>{badge}</span>;\n}\n/** @deprecated The pill now follows `deriveTaskStatus`; `labels` is ignored. */\nexport const TaskStatusChip = TaskStatusPill as (props: { readonly task: Pick<TaskRecord, 'waiting_on_you' | 'waiting_on' | 'risk'>; readonly labels?: unknown; readonly locale?: TaskLocale; readonly viewerAction?: TaskViewerAction }) => ReactNode;\n\n/** A card waiting on the viewer carries a 2px foreground edge: the outline border plus a 1px ring, both in the primary role. */\nexport function TaskAttention({ active, children }: { readonly active: boolean; readonly children: ReactNode }) {\n return active ? <div data-task-attention=\"\" className=\"rounded-[var(--radius-lg)] ring-1 ring-primary [--border:var(--primary)]\">{children}</div> : <>{children}</>;\n}\n\nexport interface TaskRequestCardProps {\n readonly task: TaskRecord;\n /** `card`: the board card (D2). `compact`: the app's request card (C7). */\n readonly variant?: 'card' | 'compact';\n readonly selected?: boolean;\n readonly href?: string;\n readonly onOpen?: ((task: TaskRecord) => void) | undefined;\n /** Host-authorized actions of the compact card, e.g. « Modifier » and « Confirmer »; rendered as two equal columns. */\n readonly actions?: ReactNode;\n readonly locale?: TaskLocale | undefined;\n /** Explicit host context for the French viewer label: « À vous » (question) or « À confirmer » (confirmation). */\n readonly viewerAction?: TaskViewerAction | undefined;\n /** @deprecated Ignored; kept for source compatibility. */\n readonly labels?: unknown;\n}\n\n/** One record, two renderings: the board card (D2) and the compact request card (C7). Actions remain host-authorized. */\nexport function TaskRequestCard({ task, variant = 'card', selected, href, onOpen, actions, locale = 'en', viewerAction }: TaskRequestCardProps) {\n const activate = (event: MouseEvent<HTMLAnchorElement>) => {\n if (!onOpen || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return;\n event.preventDefault(); onOpen(task);\n };\n const anchor = { href, onClick: activate, 'data-task-open': task.id, 'aria-current': selected ? 'true' as const : undefined };\n const link = href ? <a {...anchor} /> : undefined;\n const pill = <TaskStatusPill task={task} locale={locale} viewerAction={viewerAction} />;\n if (variant === 'compact') {\n const strings = compactStrings[locale];\n const meta = [task.screen && strings.screen(task.screen), task.captureCount !== undefined && captures(task.captureCount)].filter(Boolean).join(' · ');\n const title = link && actions ? <a {...anchor} className=\"underline-offset-4 hover:underline\">{task.title}</a> : task.title;\n return <div role=\"listitem\"><Item variant=\"outline\" className=\"flex-col items-stretch gap-2\" data-task-card={task.id} data-variant=\"compact\" {...(link && !actions ? { render: link } : {})}>\n <div className=\"flex min-w-0 items-center justify-between gap-2\"><span className=\"text-xs font-semibold text-muted-foreground\">{strings.label}</span>{pill}</div>\n <div className=\"min-w-0 text-[15px] leading-tight font-semibold break-words\">{title}</div>\n {meta && <div className=\"text-[13px] text-muted-foreground\">{meta}</div>}\n {actions && <div data-task-actions=\"\" className=\"mt-1 grid grid-cols-2 gap-2 *:min-h-11 *:w-full\">{actions}</div>}\n </Item></div>;\n }\n return <div role=\"listitem\" className={cn('rounded-[var(--radius-lg)] bg-card', taskTokens.card)}><TaskAttention active={task.waiting_on_you === true}>\n <Item variant=\"outline\" className=\"flex-col items-stretch gap-1\" data-task-card={task.id} data-selected={selected || undefined} {...(link ? { render: link } : {})}>\n <span className=\"flex min-h-5 min-w-0 items-center justify-between gap-2 text-[11px] text-muted-foreground\"><span>{taskMeta(task)}</span>{pill}</span>\n <span className=\"min-w-0 text-sm break-words text-foreground\">{task.title}</span>\n </Item>\n </TaskAttention></div>;\n}\n",
|
|
24
|
+
"type": "registry:component",
|
|
25
|
+
"target": "components/task-board/task-request-card.tsx"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "registry/task-board/task-detail.tsx",
|
|
29
|
+
"content": "'use client';\n\nimport { useState } from 'react';\nimport type { ReactNode } from 'react';\nimport { AppWindowIcon, ChevronRightIcon, FileIcon, ImageIcon } from 'lucide-react';\nimport type { TaskFile, TaskQuestion, TaskRecord } from '@hachej/boring-ui-kit/task-board';\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { Item } from '@/components/ui/item';\nimport { TaskAttention, taskTokens } from './task-request-card';\n\nexport interface TaskDetailProps {\n readonly task: TaskRecord;\n /** Host content placed after the title card (for example a mount report or host-authorized request actions). */\n readonly actions?: ReactNode;\n /** The host reauthorizes and resolves the original question. Rendering never supplies approval. */\n readonly onAnswer?: ((task: TaskRecord, question: TaskQuestion, value: string) => void) | undefined;\n /** « Other… » opens an inline field; Send passes the typed text. No synthetic option value or approval is generated. */\n readonly onOther?: ((task: TaskRecord, question: TaskQuestion, text: string) => void) | undefined;\n /** « Discuss in chat »: the host stages `@ D-201 · <title>` and the question in its one composer, without sending. */\n readonly onDiscussQuestion?: ((task: TaskRecord, question: TaskQuestion) => void) | undefined;\n readonly onOpenFile?: ((task: TaskRecord, file: TaskFile) => void) | undefined;\n /** « Ask in the conversation »: the host focuses its composer with the task mention. */\n readonly onAsk?: ((task: TaskRecord) => void) | undefined;\n /** @deprecated Ignored; kept for source compatibility. */\n readonly labels?: unknown;\n}\nconst preview = (value: string) => value.length > 160 ? `${value.slice(0, 160)}…` : value;\nconst sectionLabel = 'text-[11px] font-semibold tracking-[0.06em] text-muted-foreground';\nconst kinds = { markdown: ['Markdown', FileIcon], mockup: ['Mockup', AppWindowIcon], capture: ['Capture', ImageIcon] } as const;\nconst fileKind = (file: TaskFile) => file.kind ?? (/\\.(?:md|markdown)$/i.test(file.path) ? 'markdown' : /\\.html?$/i.test(file.path) ? 'mockup' : /\\.(?:png|jpe?g|gif|webp|svg)$/i.test(file.path) ? 'capture' : undefined);\n/** ISO times show as local HH:MM; host-formatted strings show as given. */\nconst clock = (value: string) => /^\\d{4}-\\d\\d-\\d\\dT/.test(value) && Number.isFinite(Date.parse(value)) ? new Date(value).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }) : value;\n\nfunction AnsweredQuestion({ question }: { readonly question: TaskQuestion }) {\n const [expanded, setExpanded] = useState(false);\n return <li data-task-answered={question.id} className=\"min-w-0\">\n <Button variant=\"ghost\" className=\"grid h-auto w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] justify-normal gap-x-2 gap-y-0.5 text-start whitespace-normal\" aria-expanded={expanded} onClick={() => setExpanded(value => !value)}>\n <span className=\"truncate text-[13px] font-normal\">{preview(question.text)}</span>\n <ChevronRightIcon aria-hidden=\"true\" className=\"text-muted-foreground\" />\n <span data-task-answer={question.id} className=\"col-span-full truncate text-[13px] font-normal text-muted-foreground\">{question.answeredBy ? `${preview(question.answeredBy)}: ` : ''}{preview(question.answer ?? '')}</span>\n </Button>\n {expanded && <div data-task-answer-full className=\"flex flex-col gap-1 px-2 pb-2 text-[13px] break-words whitespace-pre-wrap\"><p>{question.text}</p><p className=\"text-muted-foreground\">{question.answeredBy && <>{question.answeredBy}: </>}{question.answer}</p></div>}\n </li>;\n}\n\nfunction OpenQuestion({ task, question, onAnswer, onOther, onDiscussQuestion }: Pick<TaskDetailProps, 'task' | 'onAnswer' | 'onOther' | 'onDiscussQuestion'> & { readonly question: TaskQuestion }) {\n const [other, setOther] = useState<string | null>(null);\n const send = () => { const text = other?.trim(); if (text) { onOther?.(task, question, text); setOther(null); } };\n const asked = [question.askedBy, question.askedAt && clock(question.askedAt)].filter(Boolean).join(' · ');\n return <TaskAttention active>\n <Item variant=\"outline\" className=\"flex-col items-stretch gap-2\" data-task-question={question.id}>\n <div className=\"flex items-baseline justify-between gap-2\"><span className={sectionLabel}>QUESTION FOR YOU</span>{asked && <span className=\"text-[11px] text-muted-foreground\">{asked}</span>}</div>\n <p className=\"text-sm\">{question.text}</p>\n <div className={`flex items-center gap-2 ${taskTokens.control}`}>\n {question.options?.map((option, index) => {\n const value = typeof option === 'string' ? option : option.value;\n const label = typeof option === 'string' ? option : option.label;\n return <Button key={index} variant={index === 0 ? 'default' : 'outline'} disabled={!onAnswer} onClick={() => onAnswer?.(task, question, value)}>{label}</Button>;\n })}\n {onOther && <Button variant=\"outline\" data-task-other={question.id} aria-expanded={other !== null} onClick={() => setOther(value => value === null ? '' : null)}>Other…</Button>}\n {onDiscussQuestion && <Button variant=\"link\" className=\"h-auto min-w-0 shrink text-center leading-tight whitespace-normal underline\" data-task-discuss={question.id} onClick={() => onDiscussQuestion(task, question)}>Discuss in chat</Button>}\n </div>\n {other !== null && <form className={`flex gap-2 ${taskTokens.control}`} data-task-other-form={question.id} onSubmit={event => { event.preventDefault(); send(); }}>\n <Input autoFocus aria-label=\"Your answer\" maxLength={4096} value={other} onChange={event => setOther(event.target.value)} onKeyDown={event => { if (event.key === 'Escape') { event.preventDefault(); setOther(null); } }} />\n <Button type=\"submit\" disabled={!other.trim()}>Send</Button>\n </form>}\n </Item>\n </TaskAttention>;\n}\n\n/** Task detail (spec 3 §4), in order: title, the open question for the viewer, Answered, Files, « Ask in the conversation ». */\nexport function TaskDetail({ task, actions, onAnswer, onOther, onDiscussQuestion, onOpenFile, onAsk }: TaskDetailProps) {\n const [showAll, setShowAll] = useState(false);\n const open = task.waiting_on_you === true ? task.questions?.find(question => question.answer === undefined) : undefined;\n const answered = task.questions?.filter(question => question.answer !== undefined).toReversed().sort((a, b) => (Date.parse(b.answeredAt ?? '') || 0) - (Date.parse(a.answeredAt ?? '') || 0)) ?? [];\n const meta = [task.screen && `Screen: ${task.screen}`, task.requester && `asked by ${task.requester}`, task.refiner && `refined by ${task.refiner}`].filter(Boolean).join(' · ');\n const folder = task.folder?.replace(/\\/?$/, '/');\n return <div className=\"flex min-w-0 flex-col gap-2.5\" data-task-detail={task.id}>\n <div className=\"rounded-[var(--radius-lg)] bg-card\"><Item variant=\"outline\" className=\"flex-col items-stretch gap-1\" data-task-title-card=\"\">\n <h2 className=\"text-base font-semibold break-words\">{task.title}</h2>\n {meta && <p className=\"text-xs text-muted-foreground\">{meta}</p>}\n </Item></div>\n {actions}\n {open && <div className=\"rounded-[var(--radius-lg)] bg-card\"><OpenQuestion key={open.id} task={task} question={open} onAnswer={onAnswer} onOther={onOther} onDiscussQuestion={onDiscussQuestion} /></div>}\n {!!answered.length && <section aria-label=\"Answered questions\" className=\"rounded-[var(--radius-lg)] bg-card\"><Item variant=\"outline\" className=\"flex-col items-stretch gap-0\">\n <div className=\"flex items-baseline justify-between gap-2 pl-2.5\"><h3 className={sectionLabel}>ANSWERED · {answered.length}</h3>\n {answered.length > 3 && <Button variant=\"link\" size=\"xs\" className=\"underline\" data-task-show-answers aria-expanded={showAll} onClick={() => setShowAll(value => !value)}>{showAll ? 'Show latest' : 'Show all'}</Button>}</div>\n <ul className=\"flex min-w-0 flex-col\">{(showAll ? answered : answered.slice(0, 3)).map(question => <AnsweredQuestion key={question.id} question={question} />)}</ul>\n </Item></section>}\n {!!task.files?.length && <section aria-label=\"Task files\" className=\"rounded-[var(--radius-lg)] bg-card\"><Item variant=\"outline\" className=\"flex-col items-stretch gap-0.5\">\n <h3 className={`${sectionLabel} px-2.5 pb-1`}>FILES{folder && ` · ${folder}`}</h3>\n {task.files.map(file => {\n const kind = fileKind(file), [label, Icon] = kind ? kinds[kind] : ['', FileIcon];\n const name = file.name ?? (folder && file.path.startsWith(folder) ? file.path.slice(folder.length) : file.path.split('/').pop());\n const content = <><Icon aria-hidden=\"true\" /><span className=\"min-w-0 flex-1 truncate text-start text-[13px] font-normal\">{name}</span>{label && <span className=\"text-[11px] font-normal text-muted-foreground\">{label}</span>}</>;\n return onOpenFile ? <Button key={file.path} variant=\"ghost\" className=\"w-full justify-start\" data-task-file={file.path} onClick={() => onOpenFile(task, file)}>{content}</Button> : <p key={file.path} className=\"flex items-center gap-2\">{content}</p>;\n })}\n </Item></section>}\n {onAsk && <Button variant=\"link\" className=\"self-start underline\" data-task-ask={task.id} onClick={() => onAsk(task)}>Ask in the conversation</Button>}\n </div>;\n}\n",
|
|
30
|
+
"type": "registry:component",
|
|
31
|
+
"target": "components/task-board/task-detail.tsx"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"type": "registry:block"
|
|
35
|
+
}
|
package/r/task-list-viewer.json
CHANGED
|
@@ -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.4.
|
|
8
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.31",
|
|
8
|
+
"@hachej/boring-files@^0.4.31",
|
|
9
9
|
"react@^19.0.0",
|
|
10
10
|
"react-dom@^19.0.0",
|
|
11
11
|
"zod@^4.6.5"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "task-mentions",
|
|
4
|
+
"title": "Task mentions",
|
|
5
|
+
"description": "Optional authorized task catalog for the existing PM composer, composed with host file search.",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.31",
|
|
8
|
+
"react@^19.0.0",
|
|
9
|
+
"lucide-react@^1.51.0"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/task-board.json",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/pi-chat.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/pi-chat-mentions.json"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
{
|
|
18
|
+
"path": "registry/task-mentions/task-mentions.tsx",
|
|
19
|
+
"content": "import { ListTodoIcon } from 'lucide-react';\nimport { taskReference } from '@hachej/boring-ui-kit/task-board';\nimport type { TaskRecord } from '@hachej/boring-ui-kit/task-board';\nimport type { MentionsConfig } from '../pi-chat/config';\nimport { MentionPicker } from '../pi-chat-mentions/mention-menu';\n\n/** Authorized tasks only. Composes, never replaces, the host's file search and other mention kinds. */\nexport function taskMentions({ tasks, onOpen, base }: {\n readonly tasks: readonly TaskRecord[];\n readonly onOpen: (task: TaskRecord) => void;\n readonly base?: MentionsConfig;\n}): MentionsConfig {\n const find = (id: string) => tasks.find(task => task.id === id || taskReference(task) === id);\n const label = (id: string) => { const task = find(id); return task ? `@ ${taskReference(task)} · ${task.title}` : id; };\n return {\n ...base,\n Picker: base?.Picker ?? MentionPicker,\n listLabel: base?.listLabel ?? 'Tasks and files',\n kinds: { ...base?.kinds, task: { label, icon: <ListTodoIcon aria-hidden=\"true\" />, open: id => { const task = find(id); if (task) onOpen(task); } } },\n search: async (query, signal) => {\n const needle = query.toLocaleLowerCase();\n const matches = tasks.filter(task => `${taskReference(task)} ${task.title}`.toLocaleLowerCase().includes(needle))\n .sort((a, b) => Number(b.waiting_on_you === true) - Number(a.waiting_on_you === true))\n .map(task => ({ path: `task:${task.id}`, label: `${taskReference(task)} · ${task.title}${task.waiting_on_you ? ' · Waiting on you' : ''}` }));\n const files = await base?.search(query, signal) ?? [];\n return signal.aborted ? [] : [...matches, ...files];\n },\n };\n}\n",
|
|
20
|
+
"type": "registry:component",
|
|
21
|
+
"target": "components/task-mentions/task-mentions.tsx"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"type": "registry:component"
|
|
25
|
+
}
|
package/r/theme.json
CHANGED
|
@@ -40,7 +40,34 @@
|
|
|
40
40
|
"shadow-md": "var(--shadow-md)",
|
|
41
41
|
"shadow-lg": "var(--shadow-lg)",
|
|
42
42
|
"shadow-xl": "var(--shadow-xl)",
|
|
43
|
-
"shadow-2xl": "var(--shadow-2xl)"
|
|
43
|
+
"shadow-2xl": "var(--shadow-2xl)",
|
|
44
|
+
"color-pm-mark": "var(--pm-mark)",
|
|
45
|
+
"color-pm-mark-foreground": "var(--pm-mark-foreground)",
|
|
46
|
+
"color-pm-mic": "var(--pm-mic)",
|
|
47
|
+
"color-pm-mic-foreground": "var(--pm-mic-foreground)",
|
|
48
|
+
"color-pm-send-idle": "var(--pm-send-idle)",
|
|
49
|
+
"color-pm-line": "var(--pm-line)",
|
|
50
|
+
"color-pm-strip-line": "var(--pm-strip-line)",
|
|
51
|
+
"radius-pm-box": "var(--pm-radius-box)",
|
|
52
|
+
"radius-pm-sheet": "var(--pm-radius-sheet)",
|
|
53
|
+
"shadow-pm-sheet": "var(--pm-shadow-sheet)",
|
|
54
|
+
"shadow-pm-panel": "var(--pm-shadow-panel)",
|
|
55
|
+
"font-weight-pm-title": "var(--pm-weight-title)",
|
|
56
|
+
"color-pm-hint": "var(--pm-hint)",
|
|
57
|
+
"radius-pm-round": "var(--pm-radius-round)",
|
|
58
|
+
"radius-pm-toggle": "var(--pm-radius-toggle)",
|
|
59
|
+
"radius-pm-segment": "var(--pm-radius-segment)",
|
|
60
|
+
"radius-pm-control": "var(--pm-radius-control)",
|
|
61
|
+
"color-pm-bubble": "var(--pm-bubble)",
|
|
62
|
+
"color-pm-bubble-foreground": "var(--pm-bubble-foreground)",
|
|
63
|
+
"color-pm-reply": "var(--pm-reply)",
|
|
64
|
+
"radius-pm-bubble": "var(--pm-radius-bubble)",
|
|
65
|
+
"radius-pm-tail": "var(--pm-radius-tail)",
|
|
66
|
+
"radius-pm-token": "var(--pm-radius-token)",
|
|
67
|
+
"radius-pm-popover": "var(--pm-radius-popover)",
|
|
68
|
+
"radius-pm-send": "var(--pm-radius-send)",
|
|
69
|
+
"shadow-pm-popover": "var(--pm-shadow-popover)",
|
|
70
|
+
"color-pm-label": "var(--pm-label)"
|
|
44
71
|
},
|
|
45
72
|
"light": {
|
|
46
73
|
"radius": "0.75rem",
|
|
@@ -73,7 +100,36 @@
|
|
|
73
100
|
"shadow-md": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
|
|
74
101
|
"shadow-lg": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
|
|
75
102
|
"shadow-xl": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
|
|
76
|
-
"shadow-2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)"
|
|
103
|
+
"shadow-2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)",
|
|
104
|
+
"pm-mark": "var(--primary)",
|
|
105
|
+
"pm-mark-foreground": "var(--primary-foreground)",
|
|
106
|
+
"pm-mic": "transparent",
|
|
107
|
+
"pm-mic-foreground": "var(--foreground)",
|
|
108
|
+
"pm-send-idle": "var(--muted-foreground)",
|
|
109
|
+
"pm-line": "var(--input)",
|
|
110
|
+
"pm-strip-line": "var(--muted-foreground)",
|
|
111
|
+
"pm-radius-box": "0.875rem",
|
|
112
|
+
"pm-radius-sheet": "1.25rem",
|
|
113
|
+
"pm-shadow-sheet": "0 -8px 24px rgb(0 0 0 / 0.12)",
|
|
114
|
+
"pm-shadow-panel": "0 -8px 24px rgb(0 0 0 / 0.1)",
|
|
115
|
+
"pm-weight-title": "650",
|
|
116
|
+
"pm-hint": "var(--muted-foreground)",
|
|
117
|
+
"pm-radius-round": "9999px",
|
|
118
|
+
"pm-radius-toggle": "9999px",
|
|
119
|
+
"pm-radius-segment": "9999px",
|
|
120
|
+
"pm-radius-control": "var(--radius)",
|
|
121
|
+
"pm-bubble": "var(--muted)",
|
|
122
|
+
"pm-bubble-foreground": "var(--foreground)",
|
|
123
|
+
"pm-reply": "transparent",
|
|
124
|
+
"pm-radius-bubble": "1rem",
|
|
125
|
+
"pm-radius-tail": "0.25rem",
|
|
126
|
+
"pm-radius-token": "0.375rem",
|
|
127
|
+
"pm-radius-popover": "0.75rem",
|
|
128
|
+
"pm-radius-send": "0.5rem",
|
|
129
|
+
"pm-shadow-popover": "0 0 0 1px var(--border), 0 8px 24px rgb(0 0 0 / 0.14)",
|
|
130
|
+
"pm-label": "var(--foreground)",
|
|
131
|
+
"pm-mark-line": "35%",
|
|
132
|
+
"pm-mark-width": "2px"
|
|
77
133
|
},
|
|
78
134
|
"dark": {
|
|
79
135
|
"background": "oklch(0.175 0.006 285.885)",
|
|
@@ -105,7 +161,36 @@
|
|
|
105
161
|
"shadow-md": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
|
|
106
162
|
"shadow-lg": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
|
|
107
163
|
"shadow-xl": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
|
|
108
|
-
"shadow-2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)"
|
|
164
|
+
"shadow-2xl": "0 25px 50px -12px rgb(0 0 0 / 0.25)",
|
|
165
|
+
"pm-mark": "var(--primary)",
|
|
166
|
+
"pm-mark-foreground": "var(--primary-foreground)",
|
|
167
|
+
"pm-mic": "transparent",
|
|
168
|
+
"pm-mic-foreground": "var(--foreground)",
|
|
169
|
+
"pm-send-idle": "var(--muted-foreground)",
|
|
170
|
+
"pm-line": "var(--input)",
|
|
171
|
+
"pm-strip-line": "var(--muted-foreground)",
|
|
172
|
+
"pm-radius-box": "0.875rem",
|
|
173
|
+
"pm-radius-sheet": "1.25rem",
|
|
174
|
+
"pm-shadow-sheet": "0 -8px 24px rgb(0 0 0 / 0.12)",
|
|
175
|
+
"pm-shadow-panel": "0 -8px 24px rgb(0 0 0 / 0.1)",
|
|
176
|
+
"pm-weight-title": "650",
|
|
177
|
+
"pm-hint": "var(--muted-foreground)",
|
|
178
|
+
"pm-radius-round": "9999px",
|
|
179
|
+
"pm-radius-toggle": "9999px",
|
|
180
|
+
"pm-radius-segment": "9999px",
|
|
181
|
+
"pm-radius-control": "var(--radius)",
|
|
182
|
+
"pm-bubble": "var(--muted)",
|
|
183
|
+
"pm-bubble-foreground": "var(--foreground)",
|
|
184
|
+
"pm-reply": "transparent",
|
|
185
|
+
"pm-radius-bubble": "1rem",
|
|
186
|
+
"pm-radius-tail": "0.25rem",
|
|
187
|
+
"pm-radius-token": "0.375rem",
|
|
188
|
+
"pm-radius-popover": "0.75rem",
|
|
189
|
+
"pm-radius-send": "0.5rem",
|
|
190
|
+
"pm-shadow-popover": "0 0 0 1px var(--border), 0 8px 24px rgb(0 0 0 / 0.14)",
|
|
191
|
+
"pm-label": "var(--foreground)",
|
|
192
|
+
"pm-mark-line": "35%",
|
|
193
|
+
"pm-mark-width": "2px"
|
|
109
194
|
}
|
|
110
195
|
},
|
|
111
196
|
"type": "registry:theme"
|
package/r/version-history.json
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"title": "Version history rows",
|
|
5
5
|
"description": "Shared view-only version rows and menu metadata with relative time, provenance and Current. No file reads, publication or restoration.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-files@^0.4.31",
|
|
8
8
|
"react@^19.0.0"
|
|
9
9
|
],
|
|
10
10
|
"registryDependencies": [
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/button.json"
|
|
12
12
|
],
|
|
13
13
|
"files": [
|
|
14
14
|
{
|
package/r/viewer-code.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"lucide-react@^1.51.0"
|
|
9
9
|
],
|
|
10
10
|
"registryDependencies": [
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-frame.json",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/pi-chat.json"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
package/r/viewer-frame.json
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
"title": "Viewer frame",
|
|
5
5
|
"description": "The standard viewer top bar every viewer draws in (title, status, viewer controls, Refresh, Share, Copy, Download, Open in new tab, Close, overflow menu), its window labels, download, share and media helpers and the sandboxed interactive HTML frame. The viewers' styles live here. Each viewer is its own item (viewer-markdown, viewer-html, viewer-image, viewer-pdf, viewer-code) with a cheap kind module and a view loaded on first open.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.31",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"react-dom@^19.0.0",
|
|
10
10
|
"lucide-react@^1.51.0"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
15
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/utils.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/button.json",
|
|
15
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/version-history.json"
|
|
16
16
|
],
|
|
17
17
|
"files": [
|
|
18
18
|
{
|
package/r/viewer-html.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"title": "HTML viewer",
|
|
5
5
|
"description": "The HTML kind: `htmlKind` (match, loader) and `HtmlPane`, the passive HTML preview (sandboxed when the host opts in) and source editor, in the viewer frame. The pane loads the first time an HTML file opens.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.31",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0"
|
|
10
10
|
],
|
|
11
11
|
"registryDependencies": [
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-frame.json",
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/utils.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/button.json"
|
|
15
15
|
],
|
|
16
16
|
"files": [
|
|
17
17
|
{
|
package/r/viewer-image.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"lucide-react@^1.51.0"
|
|
9
9
|
],
|
|
10
10
|
"registryDependencies": [
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-frame.json",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/utils.json"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
package/r/viewer-markdown.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Markdown viewer",
|
|
5
5
|
"description": "The Markdown kind: `markdownKind` (match, loader) and `MarkdownPane`, the tiptap editor with a formatting toolbar and prose styling over the kit's revisioned Markdown controller, in the viewer frame. The pane and tiptap load the first time a Markdown file opens.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-ui-kit@^0.4.
|
|
7
|
+
"@hachej/boring-ui-kit@^0.4.31",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0",
|
|
10
10
|
"marked@^17.0.6",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"@tiptap/extension-floating-menu@^3.31.4"
|
|
23
23
|
],
|
|
24
24
|
"registryDependencies": [
|
|
25
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
26
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
27
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
28
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
25
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-frame.json",
|
|
26
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/markdown-editor.json",
|
|
27
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/utils.json",
|
|
28
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/button.json"
|
|
29
29
|
],
|
|
30
30
|
"files": [
|
|
31
31
|
{
|
package/r/viewer-pdf.json
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"title": "PDF viewer",
|
|
5
5
|
"description": "The PDF kind: `pdfKind` (match, loader) and `PdfPane` in the viewer frame (the browser's viewer, or pdf.js pages where there is none), and LocalFileViewer for browser-only files. The pane loads the first time a PDF opens.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hachej/boring-files@^0.4.
|
|
7
|
+
"@hachej/boring-files@^0.4.31",
|
|
8
8
|
"react@^19.0.0",
|
|
9
9
|
"lucide-react@^1.51.0",
|
|
10
10
|
"pdfjs-dist@6.4.299"
|
|
11
11
|
],
|
|
12
12
|
"registryDependencies": [
|
|
13
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
14
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
13
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-frame.json",
|
|
14
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-image.json"
|
|
15
15
|
],
|
|
16
16
|
"files": [
|
|
17
17
|
{
|
package/r/viewers.json
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
"title": "Viewers",
|
|
5
5
|
"description": "Deprecated aggregate: installs every viewer item (viewer-frame, viewer-markdown, viewer-html, viewer-image, viewer-pdf, viewer-code). Add the items you use instead.",
|
|
6
6
|
"registryDependencies": [
|
|
7
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
8
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
9
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
10
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
11
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
12
|
-
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.
|
|
7
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-frame.json",
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-markdown.json",
|
|
9
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-html.json",
|
|
10
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-image.json",
|
|
11
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-pdf.json",
|
|
12
|
+
"https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.31/r/viewer-code.json"
|
|
13
13
|
],
|
|
14
14
|
"files": [],
|
|
15
15
|
"type": "registry:component"
|
package/r/windows.json
CHANGED