efficient-shell 4.132.0 → 4.134.0

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.
Files changed (111) hide show
  1. package/README.md +62 -12
  2. package/dist/{Browser-XPVJ2K7B.js → Browser-O3QZGUF2.js} +11 -11
  3. package/dist/{Browser-XPVJ2K7B.js.map → Browser-O3QZGUF2.js.map} +1 -1
  4. package/dist/{Calculator-ZXJPXG7F.js → Calculator-L6N5X3KU.js} +9 -9
  5. package/dist/{Calculator-ZXJPXG7F.js.map → Calculator-L6N5X3KU.js.map} +1 -1
  6. package/dist/ConfirmDialog-LYSSZSUD.js +4 -0
  7. package/dist/{ConfirmDialog-MTC7QMUJ.js.map → ConfirmDialog-LYSSZSUD.js.map} +1 -1
  8. package/dist/{CurrencyConverter-WSJ2BBSX.js → CurrencyConverter-64MKXB3D.js} +9 -9
  9. package/dist/{CurrencyConverter-WSJ2BBSX.js.map → CurrencyConverter-64MKXB3D.js.map} +1 -1
  10. package/dist/{Documents-ONGSXUJI.js → Documents-5WXXYCLI.js} +11 -11
  11. package/dist/{Documents-ONGSXUJI.js.map → Documents-5WXXYCLI.js.map} +1 -1
  12. package/dist/{Files-GQZMFWXQ.js → Files-AUURUUDN.js} +17 -17
  13. package/dist/{Files-GQZMFWXQ.js.map → Files-AUURUUDN.js.map} +1 -1
  14. package/dist/{Notepad-HM73XAM4.js → Notepad-GPXJVPB7.js} +12 -12
  15. package/dist/{Notepad-HM73XAM4.js.map → Notepad-GPXJVPB7.js.map} +1 -1
  16. package/dist/PdfViewer-HKO6P4SR.js +5 -0
  17. package/dist/{PdfViewer-M2YGZIYX.js.map → PdfViewer-HKO6P4SR.js.map} +1 -1
  18. package/dist/{PomodoroTimer-DG3LI5CX.js → PomodoroTimer-4TQBWLP2.js} +11 -11
  19. package/dist/{PomodoroTimer-DG3LI5CX.js.map → PomodoroTimer-4TQBWLP2.js.map} +1 -1
  20. package/dist/{Preview-TUSV4UMO.js → Preview-FOZYPKM2.js} +14 -14
  21. package/dist/{Preview-TUSV4UMO.js.map → Preview-FOZYPKM2.js.map} +1 -1
  22. package/dist/{Spreadsheet-3WTXCHYQ.js → Spreadsheet-3C66BBBF.js} +12 -12
  23. package/dist/{Spreadsheet-3WTXCHYQ.js.map → Spreadsheet-3C66BBBF.js.map} +1 -1
  24. package/dist/{Stock-YMPOMUAW.js → Stock-IF4IHYLU.js} +9 -9
  25. package/dist/{Stock-YMPOMUAW.js.map → Stock-IF4IHYLU.js.map} +1 -1
  26. package/dist/{Weather-NZO3CWLH.js → Weather-725PFR7T.js} +9 -9
  27. package/dist/{Weather-NZO3CWLH.js.map → Weather-725PFR7T.js.map} +1 -1
  28. package/dist/{WorldClock-HUC6D3AD.js → WorldClock-UU4NZIQ7.js} +9 -9
  29. package/dist/{WorldClock-HUC6D3AD.js.map → WorldClock-UU4NZIQ7.js.map} +1 -1
  30. package/dist/apps/index.d.ts +2 -2
  31. package/dist/apps/index.js +28 -28
  32. package/dist/apps/index.js.map +1 -1
  33. package/dist/brand.css +1 -1
  34. package/dist/{chunk-TQURTLK2.js → chunk-2TPTHAKI.js} +5 -5
  35. package/dist/{chunk-TQURTLK2.js.map → chunk-2TPTHAKI.js.map} +1 -1
  36. package/dist/{chunk-EENVXIVU.js → chunk-4P44JXDS.js} +3 -3
  37. package/dist/{chunk-EENVXIVU.js.map → chunk-4P44JXDS.js.map} +1 -1
  38. package/dist/{chunk-CT3HVZRZ.js → chunk-5O42JXQE.js} +2 -2
  39. package/dist/chunk-5O42JXQE.js.map +1 -0
  40. package/dist/{chunk-7IN7YZAL.js → chunk-72CQWN7O.js} +3 -3
  41. package/dist/chunk-72CQWN7O.js.map +1 -0
  42. package/dist/{chunk-I464GYJJ.js → chunk-BE7FZOFU.js} +2 -2
  43. package/dist/chunk-BE7FZOFU.js.map +1 -0
  44. package/dist/{chunk-3L463T5H.js → chunk-DVZWYWSD.js} +2 -2
  45. package/dist/chunk-DVZWYWSD.js.map +1 -0
  46. package/dist/{chunk-FONEQRBH.js → chunk-ET6ID4LP.js} +3 -3
  47. package/dist/{chunk-FONEQRBH.js.map → chunk-ET6ID4LP.js.map} +1 -1
  48. package/dist/{chunk-CGXMWHQW.js → chunk-GIGV2ZRW.js} +3 -3
  49. package/dist/{chunk-CGXMWHQW.js.map → chunk-GIGV2ZRW.js.map} +1 -1
  50. package/dist/{chunk-CJYO4RVV.js → chunk-IULP5BCD.js} +5 -5
  51. package/dist/chunk-IULP5BCD.js.map +1 -0
  52. package/dist/{chunk-BJ34QOR3.js → chunk-OLJDJYSU.js} +287 -146
  53. package/dist/chunk-OLJDJYSU.js.map +1 -0
  54. package/dist/{chunk-7MA5XBWD.js → chunk-PIGDNN5F.js} +5 -5
  55. package/dist/{chunk-7MA5XBWD.js.map → chunk-PIGDNN5F.js.map} +1 -1
  56. package/dist/chunk-Q2MJVSJT.js +7 -0
  57. package/dist/{chunk-JMKYYPOF.js.map → chunk-Q2MJVSJT.js.map} +1 -1
  58. package/dist/{chunk-ZKHD4J73.js → chunk-Q3LT745N.js} +10 -10
  59. package/dist/chunk-Q3LT745N.js.map +1 -0
  60. package/dist/{chunk-CLGNTGC5.js → chunk-QAGQRNKI.js} +3 -3
  61. package/dist/{chunk-CLGNTGC5.js.map → chunk-QAGQRNKI.js.map} +1 -1
  62. package/dist/{chunk-E3TWIN7Z.js → chunk-QRL64J5F.js} +3 -3
  63. package/dist/{chunk-E3TWIN7Z.js.map → chunk-QRL64J5F.js.map} +1 -1
  64. package/dist/{chunk-TFXJRHJV.js → chunk-QSKCQUZE.js} +3 -3
  65. package/dist/{chunk-TFXJRHJV.js.map → chunk-QSKCQUZE.js.map} +1 -1
  66. package/dist/{chunk-MQPX6ERJ.js → chunk-R4OQIQYX.js} +4 -4
  67. package/dist/{chunk-MQPX6ERJ.js.map → chunk-R4OQIQYX.js.map} +1 -1
  68. package/dist/{chunk-LQTVTOSY.js → chunk-RT6DSB5Y.js} +45 -19
  69. package/dist/chunk-RT6DSB5Y.js.map +1 -0
  70. package/dist/{chunk-VWZIGZFF.js → chunk-SHQM5CK3.js} +4 -4
  71. package/dist/{chunk-VWZIGZFF.js.map → chunk-SHQM5CK3.js.map} +1 -1
  72. package/dist/{chunk-KWEX4ZAF.js → chunk-T4VQHMG4.js} +3 -3
  73. package/dist/{chunk-KWEX4ZAF.js.map → chunk-T4VQHMG4.js.map} +1 -1
  74. package/dist/{chunk-BFBWLCO6.js → chunk-TW7LPMVR.js} +2 -2
  75. package/dist/chunk-TW7LPMVR.js.map +1 -0
  76. package/dist/{chunk-XH3ELPQZ.js → chunk-W622KQTD.js} +3 -3
  77. package/dist/chunk-W622KQTD.js.map +1 -0
  78. package/dist/{chunk-SEWF4O3H.js → chunk-X4Q6G253.js} +3 -3
  79. package/dist/chunk-X4Q6G253.js.map +1 -0
  80. package/dist/file-intake/index.js +2 -2
  81. package/dist/{index-COwksV6G.d.ts → index-DZ7E7bCl.d.ts} +196 -4
  82. package/dist/index.d.ts +3 -3
  83. package/dist/index.js +35 -35
  84. package/dist/index.js.map +1 -1
  85. package/dist/map/index.js +1 -1
  86. package/dist/markdown/index.d.ts +4 -4
  87. package/dist/markdown/index.js.map +1 -1
  88. package/dist/markup/index.d.ts +2 -2
  89. package/dist/markup/index.js.map +1 -1
  90. package/dist/page-tabs/index.js +3 -3
  91. package/dist/shell.css +1 -1
  92. package/dist/styles.css +4 -4
  93. package/dist/themes.css +3 -3
  94. package/dist/ui/index.d.ts +1 -1
  95. package/dist/ui/index.js +12 -12
  96. package/dist/ui.css +233 -8
  97. package/package.json +1 -1
  98. package/dist/ConfirmDialog-MTC7QMUJ.js +0 -4
  99. package/dist/PdfViewer-M2YGZIYX.js +0 -5
  100. package/dist/chunk-3L463T5H.js.map +0 -1
  101. package/dist/chunk-7IN7YZAL.js.map +0 -1
  102. package/dist/chunk-BFBWLCO6.js.map +0 -1
  103. package/dist/chunk-BJ34QOR3.js.map +0 -1
  104. package/dist/chunk-CJYO4RVV.js.map +0 -1
  105. package/dist/chunk-CT3HVZRZ.js.map +0 -1
  106. package/dist/chunk-I464GYJJ.js.map +0 -1
  107. package/dist/chunk-JMKYYPOF.js +0 -7
  108. package/dist/chunk-LQTVTOSY.js.map +0 -1
  109. package/dist/chunk-SEWF4O3H.js.map +0 -1
  110. package/dist/chunk-XH3ELPQZ.js.map +0 -1
  111. package/dist/chunk-ZKHD4J73.js.map +0 -1
@@ -13,5 +13,5 @@ function openFilesInTrashMode() {
13
13
  }
14
14
 
15
15
  export { getFilesDemoTree, openFilesInTrashMode, setFilesDemoTree };
16
- //# sourceMappingURL=chunk-I464GYJJ.js.map
17
- //# sourceMappingURL=chunk-I464GYJJ.js.map
16
+ //# sourceMappingURL=chunk-BE7FZOFU.js.map
17
+ //# sourceMappingURL=chunk-BE7FZOFU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/_filesShared.ts"],"names":[],"mappings":";;;AAwBA,IAAI,aAAA,GAAwC,IAAA;AAErC,SAAS,iBAAiB,IAAA,EAA8B;AAC7D,EAAA,aAAA,GAAgB,IAAA;AAClB;AAGO,SAAS,gBAAA,GAA2C;AACzD,EAAA,OAAO,aAAA;AACT;AAKO,SAAS,oBAAA,GAAuB;AACrC,EAAA,qBAAA,EAAsB;AACxB","file":"chunk-BE7FZOFU.js","sourcesContent":["/**\n * Files app config — the tiny consumer-facing surface of the Files app\n * (demo-tree injection + trash-mode open). Lives apart from Files.tsx so\n * importing these from `efficient-shell/apps` never drags the Files\n * implementation into a host's startup bundle; the Files window component\n * stays behind its React.lazy dynamic import.\n */\nimport { requestFilesTrashView } from '../shell/desktopIcons';\n\n/**\n * Demo filesystem. When a consumer (e.g. the demo app) injects a static tree\n * via `setFilesDemoTree`, Files browses it in-memory — no file server needed.\n * Browse-only: upload / new-folder / rename / delete / trash are hidden while a\n * demo tree is set. Consumers with a real file server never call it, so their\n * behaviour is unchanged.\n */\nexport interface FilesDemoNode {\n name: string;\n kind: 'file' | 'folder';\n size?: number;\n modifiedAt?: string;\n children?: FilesDemoNode[];\n}\n\nlet filesDemoTree: FilesDemoNode[] | null = null;\n\nexport function setFilesDemoTree(tree: FilesDemoNode[] | null) {\n filesDemoTree = tree;\n}\n\n/** @internal Current injected demo tree, if any — read by Files.tsx. */\nexport function getFilesDemoTree(): FilesDemoNode[] | null {\n return filesDemoTree;\n}\n\n// Side-channel for opening Files on a specific view (trash, desktop folder).\n// The flag + event plumbing lives in shell/desktopIcons.tsx; this wrapper is\n// kept because consumers import it from the package.\nexport function openFilesInTrashMode() {\n requestFilesTrashView();\n}\n"]}
@@ -22,5 +22,5 @@ function claimPdfPreviewStage(stage) {
22
22
  }
23
23
 
24
24
  export { PDF_PREVIEW_UPDATE_EVENT, claimPdfPreviewStage, peekPdfPreviewStage, setPdfPreview };
25
- //# sourceMappingURL=chunk-3L463T5H.js.map
26
- //# sourceMappingURL=chunk-3L463T5H.js.map
25
+ //# sourceMappingURL=chunk-DVZWYWSD.js.map
26
+ //# sourceMappingURL=chunk-DVZWYWSD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/_previewStage.ts"],"names":[],"mappings":";AA+CO,IAAM,wBAAA,GAA2B;AAaxC,IAAI,OAAA,GAAkC,IAAA;AACtC,IAAI,SAAA,GAAY,CAAA;AAKT,SAAS,cAAc,IAAA,EAAwC;AACpE,EAAA,MAAM,QAAQ,EAAE,SAAA;AAChB,EAAA,MAAM,KAAA,GAAyB,EAAE,KAAA,EAAO,IAAA,EAAK;AAC7C,EAAA,OAAA,GAAU,KAAA;AACV,EAAA,OAAO;AAAA,IACL,OAAO,IAAA,EAAsB;AAY3B,MAAA,IAAI,OAAA,KAAY,KAAA,EAAO,KAAA,CAAM,IAAA,GAAO,IAAA;AACpC,MAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAY,wBAAA,EAA0B,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK,EAAG,CAAC,CAAA;AAAA,IACnG;AAAA,GACF;AACF;AAGO,SAAS,mBAAA,GAA8C;AAC5D,EAAA,OAAO,OAAA;AACT;AAKO,SAAS,qBAAqB,KAAA,EAA8B;AACjE,EAAA,IAAI,OAAA,KAAY,OAAO,OAAA,GAAU,IAAA;AACnC","file":"chunk-DVZWYWSD.js","sourcesContent":["/**\n * PDF Preview staging — the tiny consumer-facing surface of the Preview app.\n *\n * Lives apart from Preview.tsx so importing `setPdfPreview` (from\n * `efficient-shell/apps` or shell internals like PdfActionButton /\n * openPreviewFile) never drags the viewer implementation — and its static\n * pdfjs-dist import — into a host's startup bundle. The Preview window\n * component stays behind its React.lazy dynamic import and drains the stage\n * on mount via the @internal peek/claim helpers below.\n */\n\nexport interface PdfPreviewData {\n /** Object URL or remote URL of the PDF. Blob URLs are revoked when the window unmounts.\n * Leave blank when staging a `converting: true` placeholder; call `handle.update({...})`\n * on the handle returned by `setPdfPreview` once conversion finishes. */\n url?: string;\n /** Display name (and download filename). */\n filename: string;\n /** Renderer to use. Defaults to `'pdf'`. `'dxf'` requires the consumer to\n * have `dxf-viewer` installed (it's an optional peer dep). `'image'`\n * renders an `<img>` for raster screenshots / photos. `'3d'` covers\n * STEP / STL / OBJ / GLTF / 3MF / IGES via the optional\n * `online-3d-viewer` peer dep. */\n kind?: 'pdf' | 'dxf' | 'image' | '3d';\n /** Optional download handler — replaces the built-in \"save URL as filename\" if supplied. */\n onDownload?: () => void;\n /** Optional email handler — only shown when supplied. */\n onEmail?: () => void;\n /** Show a progress placeholder while the consumer fetches/converts the file. */\n converting?: boolean;\n /** Headline shown on the converting placeholder (e.g. \"CONVERTING DWG FILE\"). */\n convertingMessage?: string;\n}\n\n/** Handle returned by `setPdfPreview`. Holds the identity of the staged\n * payload so a later `.update()` only targets the window that picked it up\n * (not every open Preview, which would clobber unrelated windows). */\nexport interface PdfPreviewHandle {\n /** Replace the data shown in the window that consumed this staging. Safe to\n * call before that window exists — an update that lands while the payload\n * is still staged replaces the staged payload, so the window opens on the\n * resolved file instead of the placeholder it was staged with.\n * No-op only once the window that consumed the staging has been closed. */\n update(next: PdfPreviewData): void;\n}\n\n/** @internal Event carrying `.update()` payloads to the claiming window. */\nexport const PDF_PREVIEW_UPDATE_EVENT = 'react-os-shell:pdf-preview-update';\n\n/** @internal Staged payload awaiting the next Preview window mount.\n *\n * `data` is MUTABLE and deliberately so: `update()` rewrites it in place\n * while the stage is still unclaimed. Restaging a fresh object instead would\n * break the identity that `claimPdfPreviewStage` and the claimant's own\n * `consumedRef` are holding. */\nexport interface PendingPdfStage {\n token: number;\n data: PdfPreviewData;\n}\n\nlet pending: PendingPdfStage | null = null;\nlet nextToken = 0;\n\n/** Stage a PDF for the next Preview window mount. The returned handle's\n * `update()` method swaps content in *that* specific window only — use it\n * to replace a `converting: true` placeholder with the resolved file. */\nexport function setPdfPreview(data: PdfPreviewData): PdfPreviewHandle {\n const token = ++nextToken;\n const stage: PendingPdfStage = { token, data };\n pending = stage;\n return {\n update(next: PdfPreviewData) {\n // The window is opened by `openPage('/preview')` and Preview is a lazy\n // chunk, so a consumer that resolves quickly — a cached PDF, a fast\n // endpoint, a second preview in the same session — can call this before\n // any window has mounted to listen. The event is fire-and-forget, so on\n // its own it would be dropped and the window would open on the\n // placeholder and sit there for ever. Rewrite the stage as well while it\n // is still ours: whoever drains it next gets the current payload.\n //\n // `pending === stage` is exactly \"no window has claimed this yet\".\n // Claiming and binding the listener happen in the same commit (see\n // Preview.tsx), so there is no third state where both paths miss.\n if (pending === stage) stage.data = next;\n if (typeof window === 'undefined') return;\n window.dispatchEvent(new CustomEvent(PDF_PREVIEW_UPDATE_EVENT, { detail: { token, data: next } }));\n },\n };\n}\n\n/** @internal Render-phase peek — see Preview.tsx for the drain protocol. */\nexport function peekPdfPreviewStage(): PendingPdfStage | null {\n return pending;\n}\n\n/** @internal Commit-phase claim. The identity check keeps a payload staged\n * *after* the claimant's render-phase peek available for the window it\n * belongs to. */\nexport function claimPdfPreviewStage(stage: PendingPdfStage): void {\n if (pending === stage) pending = null;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { useShellStrings } from './chunk-7IN7YZAL.js';
1
+ import { useShellStrings } from './chunk-72CQWN7O.js';
2
2
  import { useBrowserState } from './chunk-IRWSRJ4K.js';
3
3
  import { useRef, useCallback, useEffect } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -98,5 +98,5 @@ function SidebarLayout({
98
98
  }
99
99
 
100
100
  export { SidebarLayout };
101
- //# sourceMappingURL=chunk-FONEQRBH.js.map
102
- //# sourceMappingURL=chunk-FONEQRBH.js.map
101
+ //# sourceMappingURL=chunk-ET6ID4LP.js.map
102
+ //# sourceMappingURL=chunk-ET6ID4LP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell/SidebarLayout.tsx"],"names":[],"mappings":"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","file":"chunk-FONEQRBH.js","sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport type { ReactNode, PointerEvent as ReactPointerEvent } from 'react';\nimport { useBrowserState } from './hydration';\nimport { useShellStrings } from './strings';\n\n/**\n * Two-pane layout with a drag-to-resize sidebar (left or right).\n *\n * Renders flush (`h-full w-full`) — pair it with a `flushBody` window so the\n * sidebar runs from just under the title bar to the very bottom with no\n * surrounding padding. The user can drag the sidebar's inner edge to resize\n * it; pass `storageKey` to persist that width across reopens, or double-click\n * the handle to reset to `defaultWidth`. Set `side=\"right\"` to put the sidebar\n * on the right (e.g. a detail-panel layout).\n *\n * @example\n * <SidebarLayout sidebar={<MyNav />} storageKey=\"todo.sidebarWidth\">\n * <MyContent />\n * </SidebarLayout>\n */\nexport interface SidebarLayoutProps {\n /** Content of the left sidebar pane. When `sidebarTop`/`sidebarBottom` are\n * set this is the scrolling middle region (the filter nav); otherwise it is\n * the whole pane. */\n sidebar: ReactNode;\n /** Optional node pinned at the TOP of the sidebar, above `sidebar`, in a\n * padded non-scrolling region (e.g. a primary \"New X\" `SidebarActionButton`).\n * The list-window convention: primary create action lives here. */\n sidebarTop?: ReactNode;\n /** Optional node pinned at the BOTTOM of the sidebar, below `sidebar`, in a\n * padded, top-bordered non-scrolling region (e.g. an \"Export CSV\" button).\n * The middle `sidebar` grows to fill, so this stays flush to the bottom. */\n sidebarBottom?: ReactNode;\n /** Content of the main pane (opposite the sidebar). */\n children: ReactNode;\n /** Which side the sidebar sits on. Default `'left'`. When `'right'`, the\n * sidebar renders on the right with the resize handle on its left edge. */\n side?: 'left' | 'right';\n /** localStorage key to persist the sidebar width across reopens. When set,\n * the last dragged width is restored on mount. Omit for session-only width. */\n storageKey?: string;\n /** Initial sidebar width in px (used when nothing is persisted). Default 256. */\n defaultWidth?: number;\n /** Minimum sidebar width in px. Default 180. */\n minWidth?: number;\n /** Maximum sidebar width in px. Default 480. */\n maxWidth?: number;\n /** Extra classes for the outer flex row. */\n className?: string;\n /** Classes for the sidebar pane (background, border, …). The pane is a flex\n * column that scrolls its own overflow. Defaults to a light-grey,\n * right-bordered sidebar. */\n sidebarClassName?: string;\n /** Classes for the main content pane. Defaults to a white background. */\n contentClassName?: string;\n}\n\nconst clamp = (n: number, lo: number, hi: number) => Math.min(Math.max(n, lo), hi);\n\nexport default function SidebarLayout({\n sidebar,\n sidebarTop,\n sidebarBottom,\n children,\n side = 'left',\n storageKey,\n defaultWidth = 256,\n minWidth = 180,\n maxWidth = 480,\n className = '',\n sidebarClassName = side === 'right'\n ? 'border-l border-gray-200 bg-gray-50'\n : 'border-r border-gray-200 bg-gray-50',\n contentClassName = 'bg-white',\n}: SidebarLayoutProps) {\n const sideRight = side === 'right';\n const strings = useShellStrings().sidebar;\n // The saved width is the READER's, and a server has no reader: it draws\n // `defaultWidth`, hydration agrees with it, and the saved width replaces it\n // in a layout effect the moment hydration ends. A page that was never\n // server-rendered reads storage on its first render, as this always did.\n const [width, setWidth] = useBrowserState<number>(() => {\n if (storageKey) {\n let saved: string | null = null;\n // Guarded: a sandboxed frame or a locked-down profile throws on access.\n try { saved = window.localStorage.getItem(storageKey); } catch { /* treat as unsaved */ }\n const n = saved ? parseInt(saved, 10) : NaN;\n if (!Number.isNaN(n)) return clamp(n, minWidth, maxWidth);\n }\n return clamp(defaultWidth, minWidth, maxWidth);\n }, clamp(defaultWidth, minWidth, maxWidth));\n\n const dragRef = useRef<{ startX: number; startWidth: number } | null>(null);\n\n const onMove = useCallback((e: PointerEvent) => {\n const d = dragRef.current;\n if (!d) return;\n // Right-side sidebar grows as the handle is dragged left → invert the delta.\n const delta = (e.clientX - d.startX) * (sideRight ? -1 : 1);\n setWidth(clamp(d.startWidth + delta, minWidth, maxWidth));\n }, [minWidth, maxWidth, sideRight]);\n\n const onUp = useCallback(() => {\n dragRef.current = null;\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n document.body.style.removeProperty('cursor');\n document.body.style.removeProperty('user-select');\n }, [onMove]);\n\n const startDrag = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {\n e.preventDefault();\n dragRef.current = { startX: e.clientX, startWidth: width };\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n }, [width, onMove, onUp]);\n\n // Persist (debounced) once the width settles.\n useEffect(() => {\n if (!storageKey) return;\n const id = window.setTimeout(() => {\n try { window.localStorage.setItem(storageKey, String(Math.round(width))); } catch { /* ignore quota / privacy mode */ }\n }, 200);\n return () => window.clearTimeout(id);\n }, [width, storageKey]);\n\n // Drop listeners if we unmount mid-drag.\n useEffect(() => () => {\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n }, [onMove, onUp]);\n\n const edge = sideRight ? 'left-0' : 'right-0';\n // With a top/bottom slot the pane becomes a 3-region flex column: a padded\n // pinned header, the scrolling middle (`sidebar`, grows to fill), and a\n // padded, top-bordered pinned footer. Without slots it renders exactly as\n // before (the whole pane scrolls) so existing consumers are unaffected.\n const hasSlots = sidebarTop != null || sidebarBottom != null;\n const sidebarPane = (\n <div className=\"relative flex h-full shrink-0 flex-col\" style={{ width }}>\n <div className={`flex h-full flex-col ${hasSlots ? 'overflow-hidden' : 'overflow-y-auto'} ${sidebarClassName}`}>\n {sidebarTop != null && (\n <div className=\"shrink-0 px-2 pt-3 pb-2\">{sidebarTop}</div>\n )}\n {hasSlots ? (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">{sidebar}</div>\n ) : (\n sidebar\n )}\n {sidebarBottom != null && (\n <div className=\"mt-auto shrink-0 border-t border-gray-200 px-2 py-3\">{sidebarBottom}</div>\n )}\n </div>\n {/* Resize handle — pinned to the sidebar's inner edge, fixed while it scrolls. */}\n <div\n onPointerDown={startDrag}\n onDoubleClick={() => setWidth(clamp(defaultWidth, minWidth, maxWidth))}\n title={strings.resize}\n className={`group/resize absolute inset-y-0 ${edge} z-10 w-2 cursor-col-resize`}\n >\n <div className={`absolute inset-y-0 ${edge} w-px bg-transparent transition-colors group-hover/resize:bg-[var(--accent-500,var(--rosh-resize-handle))]`} />\n </div>\n </div>\n );\n const contentPane = (\n <div className={`flex min-w-0 flex-1 flex-col ${contentClassName}`}>\n {children}\n </div>\n );\n\n return (\n <div className={`flex h-full w-full overflow-hidden ${className}`}>\n {sideRight ? (\n <>{contentPane}{sidebarPane}</>\n ) : (\n <>{sidebarPane}{contentPane}</>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/shell/SidebarLayout.tsx"],"names":[],"mappings":"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","file":"chunk-ET6ID4LP.js","sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport type { ReactNode, PointerEvent as ReactPointerEvent } from 'react';\nimport { useBrowserState } from './hydration';\nimport { useShellStrings } from './strings';\n\n/**\n * Two-pane layout with a drag-to-resize sidebar (left or right).\n *\n * Renders flush (`h-full w-full`) — pair it with a `flushBody` window so the\n * sidebar runs from just under the title bar to the very bottom with no\n * surrounding padding. The user can drag the sidebar's inner edge to resize\n * it; pass `storageKey` to persist that width across reopens, or double-click\n * the handle to reset to `defaultWidth`. Set `side=\"right\"` to put the sidebar\n * on the right (e.g. a detail-panel layout).\n *\n * @example\n * <SidebarLayout sidebar={<MyNav />} storageKey=\"todo.sidebarWidth\">\n * <MyContent />\n * </SidebarLayout>\n */\nexport interface SidebarLayoutProps {\n /** Content of the left sidebar pane. When `sidebarTop`/`sidebarBottom` are\n * set this is the scrolling middle region (the filter nav); otherwise it is\n * the whole pane. */\n sidebar: ReactNode;\n /** Optional node pinned at the TOP of the sidebar, above `sidebar`, in a\n * padded non-scrolling region (e.g. a primary \"New X\" `SidebarActionButton`).\n * The list-window convention: primary create action lives here. */\n sidebarTop?: ReactNode;\n /** Optional node pinned at the BOTTOM of the sidebar, below `sidebar`, in a\n * padded, top-bordered non-scrolling region (e.g. an \"Export CSV\" button).\n * The middle `sidebar` grows to fill, so this stays flush to the bottom. */\n sidebarBottom?: ReactNode;\n /** Content of the main pane (opposite the sidebar). */\n children: ReactNode;\n /** Which side the sidebar sits on. Default `'left'`. When `'right'`, the\n * sidebar renders on the right with the resize handle on its left edge. */\n side?: 'left' | 'right';\n /** localStorage key to persist the sidebar width across reopens. When set,\n * the last dragged width is restored on mount. Omit for session-only width. */\n storageKey?: string;\n /** Initial sidebar width in px (used when nothing is persisted). Default 256. */\n defaultWidth?: number;\n /** Minimum sidebar width in px. Default 180. */\n minWidth?: number;\n /** Maximum sidebar width in px. Default 480. */\n maxWidth?: number;\n /** Extra classes for the outer flex row. */\n className?: string;\n /** Classes for the sidebar pane (background, border, …). The pane is a flex\n * column that scrolls its own overflow. Defaults to a light-grey,\n * right-bordered sidebar. */\n sidebarClassName?: string;\n /** Classes for the main content pane. Defaults to a white background. */\n contentClassName?: string;\n}\n\nconst clamp = (n: number, lo: number, hi: number) => Math.min(Math.max(n, lo), hi);\n\nexport default function SidebarLayout({\n sidebar,\n sidebarTop,\n sidebarBottom,\n children,\n side = 'left',\n storageKey,\n defaultWidth = 256,\n minWidth = 180,\n maxWidth = 480,\n className = '',\n sidebarClassName = side === 'right'\n ? 'border-l border-gray-200 bg-gray-50'\n : 'border-r border-gray-200 bg-gray-50',\n contentClassName = 'bg-white',\n}: SidebarLayoutProps) {\n const sideRight = side === 'right';\n const strings = useShellStrings().sidebar;\n // The saved width is the READER's, and a server has no reader: it draws\n // `defaultWidth`, hydration agrees with it, and the saved width replaces it\n // in a layout effect the moment hydration ends. A page that was never\n // server-rendered reads storage on its first render, as this always did.\n const [width, setWidth] = useBrowserState<number>(() => {\n if (storageKey) {\n let saved: string | null = null;\n // Guarded: a sandboxed frame or a locked-down profile throws on access.\n try { saved = window.localStorage.getItem(storageKey); } catch { /* treat as unsaved */ }\n const n = saved ? parseInt(saved, 10) : NaN;\n if (!Number.isNaN(n)) return clamp(n, minWidth, maxWidth);\n }\n return clamp(defaultWidth, minWidth, maxWidth);\n }, clamp(defaultWidth, minWidth, maxWidth));\n\n const dragRef = useRef<{ startX: number; startWidth: number } | null>(null);\n\n const onMove = useCallback((e: PointerEvent) => {\n const d = dragRef.current;\n if (!d) return;\n // Right-side sidebar grows as the handle is dragged left → invert the delta.\n const delta = (e.clientX - d.startX) * (sideRight ? -1 : 1);\n setWidth(clamp(d.startWidth + delta, minWidth, maxWidth));\n }, [minWidth, maxWidth, sideRight]);\n\n const onUp = useCallback(() => {\n dragRef.current = null;\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n document.body.style.removeProperty('cursor');\n document.body.style.removeProperty('user-select');\n }, [onMove]);\n\n const startDrag = useCallback((e: ReactPointerEvent<HTMLDivElement>) => {\n e.preventDefault();\n dragRef.current = { startX: e.clientX, startWidth: width };\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n }, [width, onMove, onUp]);\n\n // Persist (debounced) once the width settles.\n useEffect(() => {\n if (!storageKey) return;\n const id = window.setTimeout(() => {\n try { window.localStorage.setItem(storageKey, String(Math.round(width))); } catch { /* ignore quota / privacy mode */ }\n }, 200);\n return () => window.clearTimeout(id);\n }, [width, storageKey]);\n\n // Drop listeners if we unmount mid-drag.\n useEffect(() => () => {\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n }, [onMove, onUp]);\n\n const edge = sideRight ? 'left-0' : 'right-0';\n // With a top/bottom slot the pane becomes a 3-region flex column: a padded\n // pinned header, the scrolling middle (`sidebar`, grows to fill), and a\n // padded, top-bordered pinned footer. Without slots it renders exactly as\n // before (the whole pane scrolls) so existing consumers are unaffected.\n const hasSlots = sidebarTop != null || sidebarBottom != null;\n const sidebarPane = (\n <div className=\"relative flex h-full shrink-0 flex-col\" style={{ width }}>\n <div className={`flex h-full flex-col ${hasSlots ? 'overflow-hidden' : 'overflow-y-auto'} ${sidebarClassName}`}>\n {sidebarTop != null && (\n <div className=\"shrink-0 px-2 pt-3 pb-2\">{sidebarTop}</div>\n )}\n {hasSlots ? (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">{sidebar}</div>\n ) : (\n sidebar\n )}\n {sidebarBottom != null && (\n <div className=\"mt-auto shrink-0 border-t border-gray-200 px-2 py-3\">{sidebarBottom}</div>\n )}\n </div>\n {/* Resize handle — pinned to the sidebar's inner edge, fixed while it scrolls. */}\n <div\n onPointerDown={startDrag}\n onDoubleClick={() => setWidth(clamp(defaultWidth, minWidth, maxWidth))}\n title={strings.resize}\n className={`group/resize absolute inset-y-0 ${edge} z-10 w-2 cursor-col-resize`}\n >\n <div className={`absolute inset-y-0 ${edge} w-px bg-transparent transition-colors group-hover/resize:bg-[var(--accent-500,var(--rosh-resize-handle))]`} />\n </div>\n </div>\n );\n const contentPane = (\n <div className={`flex min-w-0 flex-1 flex-col ${contentClassName}`}>\n {children}\n </div>\n );\n\n return (\n <div className={`flex h-full w-full overflow-hidden ${className}`}>\n {sideRight ? (\n <>{contentPane}{sidebarPane}</>\n ) : (\n <>{sidebarPane}{contentPane}</>\n )}\n </div>\n );\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { useShellStrings } from './chunk-7IN7YZAL.js';
1
+ import { useShellStrings } from './chunk-72CQWN7O.js';
2
2
  import { Fragment } from 'react';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
@@ -66,5 +66,5 @@ function Breadcrumbs({ items, separator, maxItems = 0, className }) {
66
66
  }
67
67
 
68
68
  export { Breadcrumbs };
69
- //# sourceMappingURL=chunk-CGXMWHQW.js.map
70
- //# sourceMappingURL=chunk-CGXMWHQW.js.map
69
+ //# sourceMappingURL=chunk-GIGV2ZRW.js.map
70
+ //# sourceMappingURL=chunk-GIGV2ZRW.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shell/Breadcrumbs.tsx"],"names":[],"mappings":"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","file":"chunk-CGXMWHQW.js","sourcesContent":["import { Fragment } from 'react';\nimport type { MouseEvent, ReactNode } from 'react';\nimport { useShellStrings } from './strings';\n\n/**\n * Generic breadcrumb trail. Self-contained and styled with the same Tailwind\n * utilities the shell already ships, so consumers get it for free.\n *\n * Pass an ordered list of `items` from root → current. Every crumb except the\n * last renders as a button when it has an `onClick`; the last crumb is treated\n * as the current location — rendered inert with `aria-current=\"page\"`. When the\n * trail is long, set `maxItems` to collapse the middle into an ellipsis\n * (`first … last-n` ), keeping the first and the tail visible.\n */\nexport interface BreadcrumbItem {\n /** Visible label. */\n label: ReactNode;\n /** Optional leading icon (typically a 3.5×3.5 svg). */\n icon?: ReactNode;\n /**\n * Navigate to this crumb. Omitted on the current (last) crumb.\n *\n * Receives the event, which is what makes `href` usable from a routed app:\n * the handler calls `preventDefault()` on a plain click and hands the\n * navigation to its router, while ctrl/middle/right clicks fall through to\n * the browser untouched. Without the argument an anchor could only ever do a\n * full page load, which is the thing a single-page app is avoiding.\n */\n onClick?: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void;\n /**\n * Where this crumb goes, when it is a real destination.\n *\n * A routed app needs this rather than `onClick` alone: an `<a href>` can be\n * middle-clicked into a new tab, copied, and read off the status bar before\n * committing to it — none of which a button offers, however well it behaves\n * once pressed. Given both, the anchor still calls `onClick`, so a client-side\n * router can intercept the plain-click case and leave the rest to the browser.\n */\n href?: string;\n}\n\nexport interface BreadcrumbsProps {\n items: BreadcrumbItem[];\n /** Node rendered between crumbs. Defaults to a chevron. */\n separator?: ReactNode;\n /** Collapse the middle to an ellipsis when there are more than this many\n * crumbs. `0` (default) never collapses. */\n maxItems?: number;\n className?: string;\n}\n\nconst DEFAULT_SEPARATOR = (\n <svg className=\"h-3.5 w-3.5 shrink-0 text-gray-300\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden>\n <path\n fillRule=\"evenodd\"\n d=\"M7.21 14.77a.75.75 0 0 1 .02-1.06L11.168 10 7.23 6.29a.75.75 0 1 1 1.04-1.08l4.5 4.25a.75.75 0 0 1 0 1.08l-4.5 4.25a.75.75 0 0 1-1.06-.02Z\"\n clipRule=\"evenodd\"\n />\n </svg>\n);\n\n/** Shared by the anchor and the button forms so the two cannot drift apart. */\nconst CRUMB_LINK =\n 'inline-flex min-w-0 items-center gap-1 truncate rounded px-1 -mx-1 text-gray-500 ' +\n 'transition-colors hover:text-gray-900 hover:underline rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]';\n\ntype Crumb = { kind: 'item'; item: BreadcrumbItem; isLast: boolean } | { kind: 'ellipsis' };\n\nexport default function Breadcrumbs({ items, separator, maxItems = 0, className }: BreadcrumbsProps) {\n const strings = useShellStrings().breadcrumbs;\n if (items.length === 0) return null;\n const sep = separator ?? DEFAULT_SEPARATOR;\n\n // Build the display list, collapsing the middle if `maxItems` is exceeded.\n const crumbs: Crumb[] = [];\n const lastIndex = items.length - 1;\n if (maxItems > 0 && items.length > maxItems && maxItems >= 2) {\n const tailCount = Math.max(1, maxItems - 1); // keep the first + this many from the end\n crumbs.push({ kind: 'item', item: items[0], isLast: false });\n crumbs.push({ kind: 'ellipsis' });\n for (let i = items.length - tailCount; i <= lastIndex; i++) {\n crumbs.push({ kind: 'item', item: items[i], isLast: i === lastIndex });\n }\n } else {\n items.forEach((item, i) => crumbs.push({ kind: 'item', item, isLast: i === lastIndex }));\n }\n\n return (\n <nav aria-label={strings.label} className={`min-w-0 ${className ?? ''}`}>\n <ol className=\"flex items-center gap-1.5 text-sm\">\n {crumbs.map((crumb, i) => (\n <Fragment key={i}>\n {i > 0 && <li aria-hidden className=\"flex items-center\">{sep}</li>}\n <li className=\"flex min-w-0 items-center\">\n {crumb.kind === 'ellipsis' ? (\n <span className=\"px-0.5 text-gray-400 select-none\" aria-label={strings.hidden}>…</span>\n ) : crumb.isLast || !(crumb.item.href || crumb.item.onClick) ? (\n <span\n aria-current={crumb.isLast ? 'page' : undefined}\n className={`inline-flex min-w-0 items-center gap-1 truncate ${\n crumb.isLast ? 'font-medium text-gray-900' : 'text-gray-500'\n }`}\n >\n {crumb.item.icon}\n <span className=\"truncate\">{crumb.item.label}</span>\n </span>\n ) : crumb.item.href ? (\n <a\n href={crumb.item.href}\n onClick={crumb.item.onClick}\n className={CRUMB_LINK}\n >\n {crumb.item.icon}\n <span className=\"truncate\">{crumb.item.label}</span>\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={crumb.item.onClick}\n className={CRUMB_LINK}\n >\n {crumb.item.icon}\n <span className=\"truncate\">{crumb.item.label}</span>\n </button>\n )}\n </li>\n </Fragment>\n ))}\n </ol>\n </nav>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/shell/Breadcrumbs.tsx"],"names":[],"mappings":"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","file":"chunk-GIGV2ZRW.js","sourcesContent":["import { Fragment } from 'react';\nimport type { MouseEvent, ReactNode } from 'react';\nimport { useShellStrings } from './strings';\n\n/**\n * Generic breadcrumb trail. Self-contained and styled with the same Tailwind\n * utilities the shell already ships, so consumers get it for free.\n *\n * Pass an ordered list of `items` from root → current. Every crumb except the\n * last renders as a button when it has an `onClick`; the last crumb is treated\n * as the current location — rendered inert with `aria-current=\"page\"`. When the\n * trail is long, set `maxItems` to collapse the middle into an ellipsis\n * (`first … last-n` ), keeping the first and the tail visible.\n */\nexport interface BreadcrumbItem {\n /** Visible label. */\n label: ReactNode;\n /** Optional leading icon (typically a 3.5×3.5 svg). */\n icon?: ReactNode;\n /**\n * Navigate to this crumb. Omitted on the current (last) crumb.\n *\n * Receives the event, which is what makes `href` usable from a routed app:\n * the handler calls `preventDefault()` on a plain click and hands the\n * navigation to its router, while ctrl/middle/right clicks fall through to\n * the browser untouched. Without the argument an anchor could only ever do a\n * full page load, which is the thing a single-page app is avoiding.\n */\n onClick?: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void;\n /**\n * Where this crumb goes, when it is a real destination.\n *\n * A routed app needs this rather than `onClick` alone: an `<a href>` can be\n * middle-clicked into a new tab, copied, and read off the status bar before\n * committing to it — none of which a button offers, however well it behaves\n * once pressed. Given both, the anchor still calls `onClick`, so a client-side\n * router can intercept the plain-click case and leave the rest to the browser.\n */\n href?: string;\n}\n\nexport interface BreadcrumbsProps {\n items: BreadcrumbItem[];\n /** Node rendered between crumbs. Defaults to a chevron. */\n separator?: ReactNode;\n /** Collapse the middle to an ellipsis when there are more than this many\n * crumbs. `0` (default) never collapses. */\n maxItems?: number;\n className?: string;\n}\n\nconst DEFAULT_SEPARATOR = (\n <svg className=\"h-3.5 w-3.5 shrink-0 text-gray-300\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden>\n <path\n fillRule=\"evenodd\"\n d=\"M7.21 14.77a.75.75 0 0 1 .02-1.06L11.168 10 7.23 6.29a.75.75 0 1 1 1.04-1.08l4.5 4.25a.75.75 0 0 1 0 1.08l-4.5 4.25a.75.75 0 0 1-1.06-.02Z\"\n clipRule=\"evenodd\"\n />\n </svg>\n);\n\n/** Shared by the anchor and the button forms so the two cannot drift apart. */\nconst CRUMB_LINK =\n 'inline-flex min-w-0 items-center gap-1 truncate rounded px-1 -mx-1 text-gray-500 ' +\n 'transition-colors hover:text-gray-900 hover:underline rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]';\n\ntype Crumb = { kind: 'item'; item: BreadcrumbItem; isLast: boolean } | { kind: 'ellipsis' };\n\nexport default function Breadcrumbs({ items, separator, maxItems = 0, className }: BreadcrumbsProps) {\n const strings = useShellStrings().breadcrumbs;\n if (items.length === 0) return null;\n const sep = separator ?? DEFAULT_SEPARATOR;\n\n // Build the display list, collapsing the middle if `maxItems` is exceeded.\n const crumbs: Crumb[] = [];\n const lastIndex = items.length - 1;\n if (maxItems > 0 && items.length > maxItems && maxItems >= 2) {\n const tailCount = Math.max(1, maxItems - 1); // keep the first + this many from the end\n crumbs.push({ kind: 'item', item: items[0], isLast: false });\n crumbs.push({ kind: 'ellipsis' });\n for (let i = items.length - tailCount; i <= lastIndex; i++) {\n crumbs.push({ kind: 'item', item: items[i], isLast: i === lastIndex });\n }\n } else {\n items.forEach((item, i) => crumbs.push({ kind: 'item', item, isLast: i === lastIndex }));\n }\n\n return (\n <nav aria-label={strings.label} className={`min-w-0 ${className ?? ''}`}>\n <ol className=\"flex items-center gap-1.5 text-sm\">\n {crumbs.map((crumb, i) => (\n <Fragment key={i}>\n {i > 0 && <li aria-hidden className=\"flex items-center\">{sep}</li>}\n <li className=\"flex min-w-0 items-center\">\n {crumb.kind === 'ellipsis' ? (\n <span className=\"px-0.5 text-gray-400 select-none\" aria-label={strings.hidden}>…</span>\n ) : crumb.isLast || !(crumb.item.href || crumb.item.onClick) ? (\n <span\n aria-current={crumb.isLast ? 'page' : undefined}\n className={`inline-flex min-w-0 items-center gap-1 truncate ${\n crumb.isLast ? 'font-medium text-gray-900' : 'text-gray-500'\n }`}\n >\n {crumb.item.icon}\n <span className=\"truncate\">{crumb.item.label}</span>\n </span>\n ) : crumb.item.href ? (\n <a\n href={crumb.item.href}\n onClick={crumb.item.onClick}\n className={CRUMB_LINK}\n >\n {crumb.item.icon}\n <span className=\"truncate\">{crumb.item.label}</span>\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={crumb.item.onClick}\n className={CRUMB_LINK}\n >\n {crumb.item.icon}\n <span className=\"truncate\">{crumb.item.label}</span>\n </button>\n )}\n </li>\n </Fragment>\n ))}\n </ol>\n </nav>\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
- import { useWindowMenuItem, Modal } from './chunk-ZKHD4J73.js';
1
+ import { useWindowMenuItem, Modal } from './chunk-Q3LT745N.js';
2
2
  import { navIcons } from './chunk-GA35NEND.js';
3
- import { VERSION } from './chunk-JMKYYPOF.js';
3
+ import { VERSION } from './chunk-Q2MJVSJT.js';
4
4
  import { useState, useCallback, isValidElement, cloneElement } from 'react';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
6
 
@@ -63,7 +63,7 @@ function AboutApp({ app }) {
63
63
  /* @__PURE__ */ jsxs("div", { className: "mt-4 pt-3 border-t border-gray-200 w-full", children: [
64
64
  /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-gray-500", children: [
65
65
  "Part of the ",
66
- /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-700", children: "react-os-shell" }),
66
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-700", children: "efficient-shell" }),
67
67
  " desktop environment"
68
68
  ] }),
69
69
  VERSION && /* @__PURE__ */ jsxs("p", { className: "mt-0.5 text-[10px] font-mono text-gray-400", children: [
@@ -75,5 +75,5 @@ function AboutApp({ app }) {
75
75
  }
76
76
 
77
77
  export { AboutApp, BUILTIN_APP_INFO };
78
- //# sourceMappingURL=chunk-CJYO4RVV.js.map
79
- //# sourceMappingURL=chunk-CJYO4RVV.js.map
78
+ //# sourceMappingURL=chunk-IULP5BCD.js.map
79
+ //# sourceMappingURL=chunk-IULP5BCD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/_about.tsx"],"names":[],"mappings":"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","file":"chunk-IULP5BCD.js","sourcesContent":["/**\n * Shared \"About\" dialog for the bundled apps.\n *\n * Each bundled app carries its own version (independent of the package\n * version) so app-level changes can be tracked without bumping every app at\n * once. `<AboutApp app=\"…\" />` registers an \"About <Name>\" item in the\n * window title menu and renders the dialog: app icon, name, app version and\n * the efficient-shell attribution + shell version.\n *\n * Bump an app's version in `BUILTIN_APP_INFO` whenever that app's behaviour\n * changes, alongside the package-level CHANGELOG entry.\n */\nimport { useCallback, useState, isValidElement, cloneElement, type ReactElement } from 'react';\nimport Modal, { useWindowMenuItem } from '../shell/Modal';\nimport { navIcons } from '../shell-config/nav';\nimport { VERSION } from '../version';\n\nexport interface BuiltinAppInfo {\n /** Display name — matches the window-registry label. */\n name: string;\n /** App version, independent of the package version. */\n version: string;\n /** One-line summary shown in the About dialog. */\n description: string;\n /** Registry route — used to look up the consumer-registered nav icon. */\n route: string;\n}\n\nexport const BUILTIN_APP_INFO = {\n spreadsheet: {\n name: 'Spreadsheets',\n version: '1.1.3',\n route: '/spreadsheet',\n description: 'Multi-sheet spreadsheet editor with CSV / TSV import and export.',\n },\n notepad: {\n name: 'Notepad',\n version: '1.0.0',\n route: '/notepad',\n description: 'Color-coded notes with checklists, desktop stickies and entity autolinking.',\n },\n documents: {\n name: 'Documents',\n version: '1.1.0',\n route: '/documents',\n description: 'Letter-page editor for Word documents and plain-text files — images, lists, alignment.',\n },\n preview: {\n name: 'Preview',\n version: '1.3.2',\n route: '/preview',\n description: 'Viewer for PDF documents, images, DXF drawings and 3D models — CAD-style measuring with object snap and an AutoCAD-like command bar.',\n },\n files: {\n name: 'Files',\n version: '1.1.0',\n route: '/files',\n description: 'Personal file manager with folders, uploads, quota, trash and desktop shortcut folders.',\n },\n browser: {\n name: 'Browser',\n version: '1.0.1',\n route: '/browser',\n description: 'Minimal web browser with bookmarks, history and navigation bar.',\n },\n} satisfies Record<string, BuiltinAppInfo>;\n\nexport type BuiltinAppId = keyof typeof BUILTIN_APP_INFO;\n\nconst ABOUT_MENU_ICON = (\n <svg className=\"h-4 w-4 text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z\" />\n </svg>\n);\n\n// Generic window glyph shown when the consumer hasn't registered a nav icon\n// for the app's route (same fallback shape as the Modal title bar).\nconst FALLBACK_APP_ICON = (\n <svg className=\"h-9 w-9\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 6.75A2.25 2.25 0 016 4.5h12a2.25 2.25 0 012.25 2.25v10.5A2.25 2.25 0 0118 19.5H6a2.25 2.25 0 01-2.25-2.25V6.75z M3.75 9h16.5\" />\n </svg>\n);\n\n/** Registers \"About <Name>\" in the window title menu and renders the dialog.\n * Render anywhere inside the app's window. */\nexport default function AboutApp({ app }: { app: BuiltinAppId }) {\n const info = BUILTIN_APP_INFO[app];\n const [open, setOpen] = useState(false);\n const openDialog = useCallback(() => setOpen(true), []);\n useWindowMenuItem(`About ${info.name}`, openDialog, ABOUT_MENU_ICON);\n\n if (!open) return null;\n\n const registered = navIcons[info.route];\n const appIcon = isValidElement(registered)\n ? cloneElement(registered as ReactElement, { className: 'h-9 w-9' } as any)\n : FALLBACK_APP_ICON;\n\n return (\n <Modal open onClose={() => setOpen(false)} title={`About ${info.name}`} size=\"sm\" compact bodyScroll={false} autoHeight dimensions={[320, 320]}>\n <div className=\"flex flex-col items-center text-center px-5 pt-5 pb-4\">\n <div className=\"flex h-16 w-16 items-center justify-center rounded-2xl bg-gray-100 text-gray-600 shrink-0\">\n {appIcon}\n </div>\n <h2 className=\"mt-3 text-lg font-bold text-gray-900\">{info.name}</h2>\n <p className=\"text-[11px] font-mono text-gray-400\">Version {info.version}</p>\n <p className=\"mt-2 text-xs text-gray-500 leading-relaxed\">{info.description}</p>\n <div className=\"mt-4 pt-3 border-t border-gray-200 w-full\">\n <p className=\"text-[11px] text-gray-500\">\n Part of the <span className=\"font-medium text-gray-700\">efficient-shell</span> desktop environment\n </p>\n {VERSION && <p className=\"mt-0.5 text-[10px] font-mono text-gray-400\">shell v{VERSION}</p>}\n </div>\n </div>\n </Modal>\n );\n}\n"]}