@zephytiju/prism-recent-work 0.1.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.
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # PrismRecentWorkMicroUI
2
+
3
+ Platform Prism recent-work micro-UI. Component id: `recent-work`.
4
+ Published to npm as [`@zephytiju/prism-recent-work`](https://www.npmjs.com/package/@zephytiju/prism-recent-work).
5
+
6
+ The VAULT root-level continuation strip (Guanlan File Hub page): the
7
+ `RECENT WORK / CONTINUE WHERE YOU LEFT OFF` header (titles render from component
8
+ configuration keys — Prism has no composed title node) followed by per-prototype
9
+ file-row members — kind icon, file name, descriptor line, live/sync dot —
10
+ rendered 0..N at app runtime from recent `IFileEntry` records (decision D3),
11
+ ordered by `updatedAt` with an optional recency window. It consumes the
12
+ host-seeded `recent-work.file-entries` channel and never talks to Lattice
13
+ itself. Opening a row publishes the navigation/open intent consumed by the
14
+ shell and the File Viewer's path slot (D6/D8). Composed applications (for
15
+ example Guanlan) consume it as-is; the component is platform-owned.
16
+
17
+ ## Configuration keys
18
+
19
+ | Prop | Meaning |
20
+ | --- | --- |
21
+ | `locale` | UI locale for the component-fixed strings: `"en" \| "zh-CN"` (default `"en"`) — see [i18n](#internationalization-i18n) |
22
+ | `sectionTitle` | Section header title (defaults to the locale's `sectionTitle`) |
23
+ | `sectionMeta` | Continue-where-you-left-off meta on the header's right (defaults to the locale's `sectionMeta`) |
24
+ | `columns` | Row-grid column count (default `2` — the design's default 2-column row grid; the VAULT page composition passes `3`) |
25
+ | `recencyWindowHours` | Recency window in hours: entries older than now − N hours are excluded (default no window) |
26
+ | `descriptor` | `(entry) => string` full descriptor line (default `KIND • STATUS-or-EDITED-ago • DETAIL`) |
27
+ | `insertAffordance` | Renders the `+` insert affordance on rows (insert contexts only; default off) |
28
+ | `insertLabel` | Label for the insert affordance (defaults to the locale's `insert`) |
29
+
30
+ ## Channel contract
31
+
32
+ | Direction | Kind | Id | Payload |
33
+ | --- | --- | --- | --- |
34
+ | consumes | state | `recent-work.file-entries` | `RecentFilesProjection \| null` — recent `RecentFileEntry` records (the bounded `FileEntrySummary` plus `updatedAt`, `sync`, optional `status`/`detail`) |
35
+ | publishes | state | `recent-work.opened-file` | `{ id, name, kind, sync, ontologyInterface: "IFileEntry" }` on row open — the navigation/open intent (D8): a bounded file reference the shell and the File Viewer's path slot interpret in their own way (D6); receivers fetch their own domain data (D4) |
36
+ | emits | event | `recent-work.insert-file` | same bounded `IFileEntry` reference — only when `insertAffordance` is enabled (insert contexts) |
37
+
38
+ Channel ids are string literals at every call-site so the build-time channel-graph scanner can
39
+ derive the graph. Channel reads use `usePrismStateValue`; both publications are
40
+ setter-only (`usePrismStateSetter`) / event emissions. `RecentFileEntry` carries
41
+ exactly the recency data the design specifies on top of `IFileEntry` — `updatedAt`
42
+ (ordering + "EDITED 4M AGO") and the workspace `sync` state driving the live dot.
43
+
44
+ ## Audit rule
45
+
46
+ Rendering recent files is a routine read and is NOT audit-worthy. This component
47
+ emits NO audit event. The only event it can ever emit is the config-gated
48
+ `recent-work.insert-file` (an insert-context handshake, not an observable domain
49
+ action).
50
+
51
+ ## Internationalization (i18n)
52
+
53
+ The component ships `en` and `zh-CN` locale bundles — `src/locales/en.json` / `src/locales/zh-CN.json` —
54
+ and every component-fixed UI string is resolved from them (the section title and
55
+ continue-where-you-left-off meta, the per-kind descriptor labels, the
56
+ `EDITED {ago} AGO` relativization with its `M/H/D` units, the empty-state title
57
+ and hint, the open tooltip, the sync labels, and the insert label). The component
58
+ renders no hardcoded copy.
59
+
60
+ ```json
61
+ {
62
+ "recent-work": {
63
+ "sectionTitle": "RECENT WORK",
64
+ "sectionMeta": "CONTINUE WHERE YOU LEFT OFF",
65
+ "emptyTitle": "No recent work yet",
66
+ "emptyHint": "Files you open and edit will appear here so you can pick up where you left off.",
67
+ "openFile": "Open file",
68
+ "editedAgo": "EDITED {ago} AGO",
69
+ "minutesAgo": "{count}M",
70
+ "hoursAgo": "{count}H",
71
+ "daysAgo": "{count}D",
72
+ "syncLive": "Live",
73
+ "syncSyncing": "Syncing",
74
+ "syncSynced": "Synced",
75
+ "insert": "+",
76
+ "kindFolder": "FOLDER",
77
+ "kindDossier": "DOSSIER FILE",
78
+ "kindEvidence": "EVIDENCE FILE",
79
+ "kindReference": "REFERENCE",
80
+ "kindBoard": "BOARD",
81
+ "kindWorkflowBoard": "WORKFLOW BOARD",
82
+ "kindAuditBoard": "AUDIT BOARD",
83
+ "kindWorld": "GEOVISION FILE"
84
+ }
85
+ }
86
+ ```
87
+
88
+ - `locale?: "en" | "zh-CN"` prop (default `"en"`) selects the string table per instance.
89
+ - `editedAgo` uses a simple `{ago}` placeholder interpolated with the relative
90
+ age (plain substitution, no regexes — `formatMessage` is exported from the
91
+ package entry).
92
+ - Explicit `sectionTitle` / `sectionMeta` props override the locale strings.
93
+ - **Composition-authored strings are localized by the composer; component-fixed strings live in the
94
+ locale JSONs.** An explicit `sectionTitle` override (and the `descriptor`
95
+ callback) is configuration-authored: a host with a localized section passes its
96
+ own string per locale. Entry `status`/`detail` parts are host data and stay
97
+ host-localized.
98
+ - Locale bundles are namespaced under the component id (`"recent-work"`) so a composer can
99
+ deep-merge every component's bundle into ONE UI language bundle without collisions:
100
+
101
+ ```ts
102
+ import { locales as recentWorkLocales } from "@zephytiju/prism-recent-work";
103
+ // recentWorkLocales["zh-CN"] -> { "recent-work": { … } }
104
+ const uiBundle = deepMerge(hostStrings, recentWorkLocales["zh-CN"]);
105
+ ```
106
+
107
+ The parsed bundles are exported from the package entry (`locales`, `en`, `zhCN`,
108
+ `stringsForLocale`), and the raw JSONs are also served by the `./locales/*` exports subpath
109
+ (e.g. `@zephytiju/prism-recent-work/locales/zh-CN.json`); `files` ships both `dist` and
110
+ `locales`.
111
+
112
+ ## Theme
113
+
114
+ No palette is hardcoded. Every color resolves to SEMANTIC theme tokens (`ok`,
115
+ `accent`, `threat`, `warn`, `signal`, `card`, `card-dark`, `input`, `border`,
116
+ `line`, `text`, `muted`, `deep`, `panel`) consumed as CSS variables, plus
117
+ `--mantine-font-family-monospace` for the mono typography — the palette is
118
+ supplied entirely by the host's `MantineProvider`. The local demo ships TWO
119
+ themes, both defined in `src/demo.tsx`: `geovisionTheme` (dark), mapping each
120
+ semantic token onto the exact `:root` variables of the VAULT v9 prototype (deep
121
+ bg, panel black, card, mint, red, blue, amber, purple, muted, border), and the
122
+ contrasting `latticeLightTheme` (light), mapping the SAME semantic token keys
123
+ onto a different palette — the component is skinned purely through the
124
+ surrounding `MantineProvider`. Per-kind icon hues follow the prototype's palette
125
+ (doc mint → `ok`, board amber → `warn`, world purple → `signal`, folder blue →
126
+ `accent`); the sync dot uses `ok` (dimmed when synced) / `warn` (syncing).
127
+
128
+ ## Source layout
129
+
130
+ `src/` is strictly two parts:
131
+
132
+ - Component source (what the package compiles): `RecentWork.tsx` (the section),
133
+ `FileRow.tsx` (the file-row member — the File Row sub-library axiom, owning
134
+ the open-intent publication), `index.ts` (public entry), and `src/locales/`
135
+ (`en.json`, `zh-CN.json`, `index.ts` — the i18n string bundles, their
136
+ resolver, and the `{ago}` interpolation helper).
137
+ - Demo: exactly ONE file, `src/demo.tsx` — the two host themes (GeoVision v9 +
138
+ Lattice Light), all demo test data (the six per-prototype recent files with
139
+ mixed kinds and relative recency), the channel seeding
140
+ (`seedDemoChannels`) and channel monitors, and the demo page rendering TWO
141
+ `RecentWork` instances side by side behind a global EN | 中文 language switcher
142
+ (plus per-instance switches): HOST A runs the default 2-column row grid, HOST
143
+ B the VAULT page's 3-column composition, with reseed/clear buttons.
144
+
145
+ The npm package ships `dist` (compiled component + type declarations + locale
146
+ JSONs) and the top-level `locales/` directory (the raw JSON bundles, served by
147
+ the `./locales/*` exports subpath); no demo code is published.
148
+ `scripts/copy-locales.mjs` copies the JSON bundles into both locations during
149
+ `npm run build`.
150
+
151
+ ## Local development
152
+
153
+ ```sh
154
+ npm install
155
+ npm run typecheck
156
+ npm test
157
+ npm run dev
158
+ npm run shot-demo
159
+ ```
160
+
161
+ `npm install` pulls the platform peers (`@zephytiju/prism-react`,
162
+ `@zephytiju/lattice-common-interfaces`) from the npm registry, along with the
163
+ host-side peer dependencies (`react`, `react-dom`, `@mantine/core`). When
164
+ consuming the published package, install it directly
165
+ (`npm install @zephytiju/prism-recent-work`) and provide those peer dependencies
166
+ in the host application.
167
+
168
+ The demo (`npm run dev`, entry `src/demo.tsx`) plays the host: on load it seeds
169
+ the six per-prototype recent files onto the GLOBAL recent-work channel (the
170
+ component itself makes no Lattice calls, so no mock executor is needed) and
171
+ renders TWO `RecentWork` instances side by side, each inside its own
172
+ `MantineProvider` with a different theme (GeoVision v9 dark left with the
173
+ default 2-column grid, Lattice Light right with the page's 3-column
174
+ composition) — since both instances consume the same global channels, they
175
+ always render the same rows in two skins. A global EN | 中文 segmented control
176
+ switches the `locale` prop of BOTH instances at once, and each instance carries
177
+ its own per-instance control so the two hosts can render DIFFERING locales
178
+ simultaneously. Buttons reseed the recent files or clear the channel to inspect
179
+ the guided empty state — in both themes and both languages — and the channel
180
+ monitors show the shared consumed state plus the `recent-work.opened-file`
181
+ publication. `npm run shot-demo` boots the vite dev server, drives the demo in
182
+ headless Chrome (LEFT instance `en`, RIGHT instance `zh-CN`), OPENS a recent
183
+ file row so the monitor shows the published open intent, and captures the
184
+ language switcher plus both themed instances in one shot to
185
+ `/tmp/guanlan-review/demo-recent-work-themes.png`.
186
+
187
+ ## Design record
188
+
189
+ Page design: https://qcnwge0wy4s0.feishu.cn/wiki/TLFtwgBgpiW8iWkXT7rcOyKgnAd —
190
+ Component — Recent Work Section:
191
+ https://qcnwge0wy4s0.feishu.cn/wiki/Cfu2w4uLUiwwlIkzFCMcWIktnMF — File Row
192
+ sub-library: https://qcnwge0wy4s0.feishu.cn/wiki/BmoTw4WnriMV9MkKK8HchKO8nfg —
193
+ Grouping: https://qcnwge0wy4s0.feishu.cn/wiki/X7qdw8Akeiq1w0kJBdmcgTHBnmh.
194
+ Visual reference: the page's v9 interactive HTML prototype
195
+ (`vault-standalone.html`, `.sec-l` / `.sec-r` / `.frow` / `.fic`).
@@ -0,0 +1,14 @@
1
+ export interface EmptyStateProps {
2
+ /** Locale-resolved empty-state title (e.g. "No recent work yet"). */
3
+ readonly title: string;
4
+ /** Locale-resolved empty-state hint (e.g. "Files you open and edit will appear here"). */
5
+ readonly hint: string;
6
+ }
7
+ /**
8
+ * The guided empty state rendered when the recent-work channel carries no
9
+ * projection (or an empty one) — a bordered card with a title and hint. No
10
+ * palette is hardcoded; colors resolve to semantic theme tokens supplied by
11
+ * the host's MantineProvider.
12
+ */
13
+ export declare function EmptyState({ title, hint }: EmptyStateProps): import("react").JSX.Element;
14
+ //# sourceMappingURL=EmptyState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../src/EmptyState.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0FAA0F;IAC1F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,eAAe,+BAa1D"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, Stack, Text } from "@mantine/core";
3
+ /**
4
+ * The guided empty state rendered when the recent-work channel carries no
5
+ * projection (or an empty one) — a bordered card with a title and hint. No
6
+ * palette is hardcoded; colors resolve to semantic theme tokens supplied by
7
+ * the host's MantineProvider.
8
+ */
9
+ export function EmptyState({ title, hint }) {
10
+ return (_jsx(Card, { withBorder: true, "data-testid": "recent-work-empty", children: _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { fw: 500, "data-testid": "recent-work-empty-title", children: title }), _jsx(Text, { size: "sm", "data-testid": "recent-work-empty-hint", children: hint })] }) }));
11
+ }
@@ -0,0 +1,22 @@
1
+ import type { FileRowProps } from "./fileRowTypes.js";
2
+ export type { FileRowProps, FileSyncState, RecentFileEntry, RecentFilesProjection, OpenedFile, } from "./fileRowTypes.js";
3
+ /**
4
+ * Per-prototype VAULT file row (the File Row sub-library axiom): kind icon,
5
+ * file name, descriptor line, live/sync dot — the row itself is the click
6
+ * target, and a + insert affordance appears in insert contexts only.
7
+ *
8
+ * The row OWNS the "recent-work.opened-file" channel binding: opening the row
9
+ * (click or keyboard activation — it is a real button) publishes the
10
+ * navigation/open intent, a bounded IFileEntry reference consumed by the shell
11
+ * and the File Viewer's path slot (D6/D8). The opened highlight derives from
12
+ * that same channel value, so every row bound to the channel (across hosts)
13
+ * reflects the currently opened file consistently. The optional + affordance
14
+ * emits the ephemeral "recent-work.insert-file" event instead. No Lattice
15
+ * calls; routine reads are NOT audit-worthy.
16
+ *
17
+ * No palette is hardcoded: every color resolves to semantic theme tokens
18
+ * (ok / warn / accent / card / border / muted / text / input …) supplied by
19
+ * the host's MantineProvider.
20
+ */
21
+ export declare function FileRow({ entry, strings, descriptor, insertAffordance, insertLabel, }: FileRowProps): import("react").JSX.Element;
22
+ //# sourceMappingURL=FileRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileRow.d.ts","sourceRoot":"","sources":["../src/FileRow.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAA8C,MAAM,mBAAmB,CAAC;AAGlG,YAAY,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,UAAU,GACX,MAAM,mBAAmB,CAAC;AAU3B;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,OAAO,EACP,UAAU,EACV,gBAAwB,EACxB,WAAW,GACZ,EAAE,YAAY,+BA0Hd"}
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback } from "react";
3
+ import { Stack, Text, UnstyledButton } from "@mantine/core";
4
+ import { emitPrismEvent, usePrismStateSetter, usePrismStateValue } from "@zephytiju/prism-react";
5
+ import { visualForKind } from "./fileRowIcons.js";
6
+ import { defaultDescriptor, syncLabelKeyFor, syncTokenFor } from "./fileRowFormat.js";
7
+ const MONO = "var(--mantine-font-family-monospace)";
8
+ const TEXT = "var(--mantine-color-text-filled)";
9
+ const MUTED = "var(--mantine-color-muted-filled)";
10
+ const CARD_BG = "var(--mantine-color-card-filled)";
11
+ const BORDER = "var(--mantine-color-border-filled)";
12
+ const INPUT_BG = "var(--mantine-color-input-filled)";
13
+ const ACCENT = "var(--mantine-color-accent-filled)";
14
+ /**
15
+ * Per-prototype VAULT file row (the File Row sub-library axiom): kind icon,
16
+ * file name, descriptor line, live/sync dot — the row itself is the click
17
+ * target, and a + insert affordance appears in insert contexts only.
18
+ *
19
+ * The row OWNS the "recent-work.opened-file" channel binding: opening the row
20
+ * (click or keyboard activation — it is a real button) publishes the
21
+ * navigation/open intent, a bounded IFileEntry reference consumed by the shell
22
+ * and the File Viewer's path slot (D6/D8). The opened highlight derives from
23
+ * that same channel value, so every row bound to the channel (across hosts)
24
+ * reflects the currently opened file consistently. The optional + affordance
25
+ * emits the ephemeral "recent-work.insert-file" event instead. No Lattice
26
+ * calls; routine reads are NOT audit-worthy.
27
+ *
28
+ * No palette is hardcoded: every color resolves to semantic theme tokens
29
+ * (ok / warn / accent / card / border / muted / text / input …) supplied by
30
+ * the host's MantineProvider.
31
+ */
32
+ export function FileRow({ entry, strings, descriptor, insertAffordance = false, insertLabel, }) {
33
+ const publishOpenedFile = usePrismStateSetter("recent-work.opened-file");
34
+ const openedFile = usePrismStateValue("recent-work.opened-file");
35
+ const opened = openedFile?.id === entry.id;
36
+ const resolvedDescriptor = descriptor ?? ((item) => defaultDescriptor(item, strings));
37
+ const open = useCallback(() => {
38
+ publishOpenedFile({
39
+ id: entry.id,
40
+ name: entry.name,
41
+ kind: entry.kind,
42
+ sync: entry.sync ?? "live",
43
+ ontologyInterface: "IFileEntry",
44
+ });
45
+ }, [entry, publishOpenedFile]);
46
+ const visual = visualForKind(entry.kind);
47
+ const Icon = visual.Icon;
48
+ const sync = entry.sync ?? "live";
49
+ const syncToken = syncTokenFor(sync);
50
+ return (_jsxs(UnstyledButton, { onClick: () => {
51
+ open();
52
+ }, title: strings.openFile, style: {
53
+ display: "flex",
54
+ alignItems: "center",
55
+ gap: 12,
56
+ width: "100%",
57
+ height: 62,
58
+ padding: "0 14px 0 12px",
59
+ background: CARD_BG,
60
+ border: `1px solid ${opened ? ACCENT : BORDER}`,
61
+ borderRadius: 6,
62
+ cursor: "pointer",
63
+ textAlign: "left",
64
+ ...(opened ? { boxShadow: "0 0 0 1px var(--mantine-color-accent-outline)" } : {}),
65
+ }, "data-testid": `recent-work-row-${entry.id}`, children: [_jsx("span", { style: {
66
+ width: 30,
67
+ display: "flex",
68
+ alignItems: "center",
69
+ justifyContent: "center",
70
+ flexShrink: 0,
71
+ color: `var(--mantine-color-${visual.token}-filled)`,
72
+ }, "data-testid": `recent-work-icon-${entry.id}`, children: _jsx(Icon, {}) }), _jsxs(Stack, { gap: 2, miw: 0, style: { flex: 1, alignItems: "flex-start" }, children: [_jsx(Text, { fz: 12, fw: 500, truncate: "end", style: { color: TEXT, letterSpacing: "0.01em", maxWidth: "100%" }, "data-testid": `recent-work-name-${entry.id}`, children: entry.name }), _jsx(Text, { ff: MONO, fz: 9, truncate: "end", style: { color: MUTED, letterSpacing: "0.02em", maxWidth: "100%" }, "data-testid": `recent-work-meta-${entry.id}`, children: resolvedDescriptor(entry) })] }), insertAffordance ? (_jsx(UnstyledButton, { type: "button", ff: MONO, fz: 11, fw: 500, c: "accent", style: {
73
+ flexShrink: 0,
74
+ height: 22,
75
+ padding: "0 10px",
76
+ borderRadius: 11,
77
+ background: INPUT_BG,
78
+ border: `1px solid ${BORDER}`,
79
+ }, "data-testid": `recent-work-insert-${entry.id}`, onClick: (event) => {
80
+ event.stopPropagation();
81
+ emitPrismEvent("recent-work.insert-file", {
82
+ id: entry.id,
83
+ name: entry.name,
84
+ kind: entry.kind,
85
+ sync,
86
+ ontologyInterface: "IFileEntry",
87
+ });
88
+ }, children: insertLabel ?? strings.insert })) : null, _jsx("span", { "aria-label": strings[syncLabelKeyFor(sync)], role: "img", style: {
89
+ width: 7,
90
+ height: 7,
91
+ borderRadius: "50%",
92
+ background: `var(--mantine-color-${syncToken}-filled)`,
93
+ opacity: sync === "synced" ? 0.45 : 1,
94
+ flexShrink: 0,
95
+ }, "data-testid": `recent-work-dot-${entry.id}` })] }));
96
+ }
@@ -0,0 +1,43 @@
1
+ import type { RecentFileEntry } from "./FileRow.js";
2
+ import type { RecentWorkLocale } from "./locales/index.js";
3
+ export interface RecentWorkProps {
4
+ /** Section header title (defaults to the locale's `sectionTitle`). */
5
+ readonly sectionTitle?: string;
6
+ /** Continue-where-you-left-off meta on the header's right (defaults to the locale's `sectionMeta`). */
7
+ readonly sectionMeta?: string;
8
+ /** Row-grid column count (default 2 — the design's default 2-column row grid). */
9
+ readonly columns?: number;
10
+ /** Recency window in hours: entries older than now − N hours are excluded (default no window). */
11
+ readonly recencyWindowHours?: number;
12
+ /** Supplies the full row descriptor line (defaults to kind • status-or-editedAgo • detail). */
13
+ readonly descriptor?: (entry: RecentFileEntry) => string;
14
+ /** Renders the + insert affordance on every row (insert contexts only; default off). */
15
+ readonly insertAffordance?: boolean;
16
+ /** Label for the insert affordance (defaults to the locale's `insert`). */
17
+ readonly insertLabel?: string;
18
+ /** UI locale for the component-fixed strings (default "en"). */
19
+ readonly locale?: RecentWorkLocale;
20
+ }
21
+ /**
22
+ * Platform Prism recent-work micro-UI (component id "recent-work") — the
23
+ * VAULT root-level continuation strip: the "RECENT WORK / CONTINUE WHERE YOU
24
+ * LEFT OFF" header followed by per-prototype file-row members (kind icon,
25
+ * name, descriptor, live sync dot) rendered 0..N at app runtime from recent
26
+ * IFileEntry records (decision D3). The section title is part of this
27
+ * component — Prism has no composed title node, so titles render from
28
+ * component configuration keys.
29
+ *
30
+ * Rows order by updatedAt (most recent first) and can be bounded by a
31
+ * recency-window configuration; the live dot on each row reflects that
32
+ * entry's workspace sync state. Opening a row publishes the navigation/open
33
+ * intent on "recent-work.opened-file" — consumed by the shell and the File
34
+ * Viewer's path slot (D6/D8). The component is a read-only consumer of the
35
+ * host-seeded "recent-work.file-entries" channel and makes NO Lattice calls;
36
+ * rendering recent files is a routine read and is NOT audit-worthy.
37
+ *
38
+ * No palette is hardcoded: every color resolves to semantic theme tokens
39
+ * (ok / accent / card / border / muted / text …) supplied by the host's
40
+ * MantineProvider.
41
+ */
42
+ export declare function RecentWork({ sectionTitle, sectionMeta, columns, recencyWindowHours, descriptor, insertAffordance, insertLabel, locale, }: RecentWorkProps): import("react").JSX.Element;
43
+ //# sourceMappingURL=RecentWork.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecentWork.d.ts","sourceRoot":"","sources":["../src/RecentWork.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,cAAc,CAAC;AAI3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,uGAAuG;IACvG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kGAAkG;IAClG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,CAAC;IACzD,wFAAwF;IACxF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,EACzB,YAAY,EACZ,WAAW,EACX,OAAW,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAwB,EACxB,WAAW,EACX,MAAa,GACd,EAAE,eAAe,+BAgEjB"}
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { Box, Stack } from "@mantine/core";
4
+ import { usePrismStateValue } from "@zephytiju/prism-react";
5
+ import { FileRow } from "./FileRow.js";
6
+ import { EmptyState } from "./EmptyState.js";
7
+ import { SectionHeader } from "./SectionHeader.js";
8
+ import { stringsForLocale } from "./locales/index.js";
9
+ /**
10
+ * Platform Prism recent-work micro-UI (component id "recent-work") — the
11
+ * VAULT root-level continuation strip: the "RECENT WORK / CONTINUE WHERE YOU
12
+ * LEFT OFF" header followed by per-prototype file-row members (kind icon,
13
+ * name, descriptor, live sync dot) rendered 0..N at app runtime from recent
14
+ * IFileEntry records (decision D3). The section title is part of this
15
+ * component — Prism has no composed title node, so titles render from
16
+ * component configuration keys.
17
+ *
18
+ * Rows order by updatedAt (most recent first) and can be bounded by a
19
+ * recency-window configuration; the live dot on each row reflects that
20
+ * entry's workspace sync state. Opening a row publishes the navigation/open
21
+ * intent on "recent-work.opened-file" — consumed by the shell and the File
22
+ * Viewer's path slot (D6/D8). The component is a read-only consumer of the
23
+ * host-seeded "recent-work.file-entries" channel and makes NO Lattice calls;
24
+ * rendering recent files is a routine read and is NOT audit-worthy.
25
+ *
26
+ * No palette is hardcoded: every color resolves to semantic theme tokens
27
+ * (ok / accent / card / border / muted / text …) supplied by the host's
28
+ * MantineProvider.
29
+ */
30
+ export function RecentWork({ sectionTitle, sectionMeta, columns = 2, recencyWindowHours, descriptor, insertAffordance = false, insertLabel, locale = "en", }) {
31
+ // Every component-fixed UI string comes from the bundled locale JSONs;
32
+ // configuration-provided strings (an explicit sectionTitle override, …)
33
+ // stay composition-authored.
34
+ const strings = stringsForLocale(locale);
35
+ const resolvedSectionTitle = sectionTitle ?? strings.sectionTitle;
36
+ const resolvedSectionMeta = sectionMeta ?? strings.sectionMeta;
37
+ const projection = usePrismStateValue("recent-work.file-entries");
38
+ // App-runtime member rendering (D3): sort by updatedAt descending (most
39
+ // recent first — the design's recency ordering), then apply the optional
40
+ // recency window. Malformed timestamps sink to the end and fall outside
41
+ // any window.
42
+ const entries = useMemo(() => {
43
+ const listed = [...(projection?.entries ?? [])];
44
+ listed.sort((a, b) => {
45
+ const at = Date.parse(a.updatedAt);
46
+ const bt = Date.parse(b.updatedAt);
47
+ const aTime = Number.isNaN(at) ? Number.NEGATIVE_INFINITY : at;
48
+ const bTime = Number.isNaN(bt) ? Number.NEGATIVE_INFINITY : bt;
49
+ if (bTime !== aTime) {
50
+ return bTime - aTime;
51
+ }
52
+ return a.name.localeCompare(b.name);
53
+ });
54
+ if (recencyWindowHours === undefined) {
55
+ return listed;
56
+ }
57
+ const cutoff = Date.now() - recencyWindowHours * 3_600_000;
58
+ return listed.filter((entry) => {
59
+ const time = Date.parse(entry.updatedAt);
60
+ return !Number.isNaN(time) && time >= cutoff;
61
+ });
62
+ }, [projection, recencyWindowHours]);
63
+ return (_jsxs(Stack, { gap: 10, miw: 0, "data-testid": "recent-work-section", children: [_jsx(SectionHeader, { title: resolvedSectionTitle, meta: resolvedSectionMeta }), entries.length === 0 ? (_jsx(EmptyState, { title: strings.emptyTitle, hint: strings.emptyHint })) : (_jsx(Box, { style: {
64
+ display: "grid",
65
+ gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))`,
66
+ gap: 12,
67
+ }, "data-testid": "recent-work-grid", children: entries.map((entry) => (_jsx(FileRow, { entry: entry, strings: strings, descriptor: descriptor, insertAffordance: insertAffordance, insertLabel: insertLabel }, entry.id))) }))] }));
68
+ }
@@ -0,0 +1,15 @@
1
+ export interface SectionHeaderProps {
2
+ /** Resolved section title (locale string or explicit configuration override). */
3
+ readonly title: string;
4
+ /** Resolved continue-where-you-left-off meta (locale string or override). */
5
+ readonly meta: string;
6
+ }
7
+ /**
8
+ * The Recent Work section header: the section title on the left and the
9
+ * continue-where-you-left-off meta on the right. The header is part of the
10
+ * component (Prism has no composed title node), so titles render from
11
+ * component configuration keys. No palette is hardcoded — colors resolve to
12
+ * semantic theme tokens supplied by the host's MantineProvider.
13
+ */
14
+ export declare function SectionHeader({ title, meta }: SectionHeaderProps): import("react").JSX.Element;
15
+ //# sourceMappingURL=SectionHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SectionHeader.d.ts","sourceRoot":"","sources":["../src/SectionHeader.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,kBAAkB,+BAqBhE"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Group, Text } from "@mantine/core";
3
+ /**
4
+ * The Recent Work section header: the section title on the left and the
5
+ * continue-where-you-left-off meta on the right. The header is part of the
6
+ * component (Prism has no composed title node), so titles render from
7
+ * component configuration keys. No palette is hardcoded — colors resolve to
8
+ * semantic theme tokens supplied by the host's MantineProvider.
9
+ */
10
+ export function SectionHeader({ title, meta }) {
11
+ return (_jsxs(Group, { justify: "space-between", align: "baseline", wrap: "nowrap", gap: 8, children: [_jsx(Text, { fz: 12, fw: 600, style: { color: "var(--mantine-color-text-filled)", letterSpacing: "0.02em" }, "data-testid": "recent-work-section-title", children: title }), _jsx(Text, { ff: "var(--mantine-font-family-monospace)", fz: 9, style: { color: "var(--mantine-color-muted-filled)" }, "data-testid": "recent-work-section-meta", children: meta })] }));
12
+ }
@@ -0,0 +1,9 @@
1
+ import type { RecentWorkStrings } from "./locales/index.js";
2
+ import type { FileSyncState, RecentFileEntry } from "./fileRowTypes.js";
3
+ /** Semantic token driving the live dot's color for a sync state. */
4
+ export declare function syncTokenFor(state: FileSyncState): string;
5
+ /** Locale string-table key labeling a sync state (the dot's aria-label). */
6
+ export declare function syncLabelKeyFor(state: FileSyncState): keyof RecentWorkStrings;
7
+ /** Default descriptor line: KIND • STATUS-or-EDITED-ago • DETAIL. */
8
+ export declare function defaultDescriptor(entry: RecentFileEntry, strings: RecentWorkStrings): string;
9
+ //# sourceMappingURL=fileRowFormat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileRowFormat.d.ts","sourceRoot":"","sources":["../src/fileRowFormat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA0BxE,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAEzD;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,iBAAiB,CAE7E;AAyBD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAM5F"}
@@ -0,0 +1,58 @@
1
+ import { formatMessage } from "./locales/index.js";
2
+ /** Sync state → semantic token for the live dot. */
3
+ const SYNC_TOKENS = {
4
+ live: "ok",
5
+ syncing: "warn",
6
+ synced: "ok",
7
+ };
8
+ const SYNC_LABEL_KEYS = {
9
+ live: "syncLive",
10
+ syncing: "syncSyncing",
11
+ synced: "syncSynced",
12
+ };
13
+ const KIND_LABEL_KEYS = {
14
+ folder: "kindFolder",
15
+ dossier: "kindDossier",
16
+ evidence: "kindEvidence",
17
+ reference: "kindReference",
18
+ board: "kindBoard",
19
+ "workflow-board": "kindWorkflowBoard",
20
+ "audit-board": "kindAuditBoard",
21
+ world: "kindWorld",
22
+ };
23
+ /** Semantic token driving the live dot's color for a sync state. */
24
+ export function syncTokenFor(state) {
25
+ return SYNC_TOKENS[state];
26
+ }
27
+ /** Locale string-table key labeling a sync state (the dot's aria-label). */
28
+ export function syncLabelKeyFor(state) {
29
+ return SYNC_LABEL_KEYS[state];
30
+ }
31
+ /** Locale label for an entry kind; unknown kinds fall back to the uppercased kind. */
32
+ function kindLabelFor(kind, strings) {
33
+ const key = KIND_LABEL_KEYS[kind];
34
+ return key === undefined ? kind.toUpperCase() : strings[key];
35
+ }
36
+ /** Compact relative age ("4M" / "2H" / "3D") interpolated through the locale templates. */
37
+ function formatAgo(updatedAt, strings) {
38
+ const time = Date.parse(updatedAt);
39
+ if (Number.isNaN(time)) {
40
+ return "";
41
+ }
42
+ const minutes = Math.max(1, Math.floor((Date.now() - time) / 60_000));
43
+ if (minutes < 60) {
44
+ return formatMessage(strings.minutesAgo, { count: minutes });
45
+ }
46
+ const hours = Math.floor(minutes / 60);
47
+ if (hours < 24) {
48
+ return formatMessage(strings.hoursAgo, { count: hours });
49
+ }
50
+ return formatMessage(strings.daysAgo, { count: Math.floor(hours / 24) });
51
+ }
52
+ /** Default descriptor line: KIND • STATUS-or-EDITED-ago • DETAIL. */
53
+ export function defaultDescriptor(entry, strings) {
54
+ const middle = entry.status ?? formatMessage(strings.editedAgo, { ago: formatAgo(entry.updatedAt, strings) });
55
+ return [kindLabelFor(entry.kind, strings), middle, entry.detail]
56
+ .filter((part) => part !== undefined && part !== "")
57
+ .join(" • ");
58
+ }
@@ -0,0 +1,10 @@
1
+ import type { ReactElement } from "react";
2
+ type IconComponent = () => ReactElement;
3
+ interface KindVisual {
4
+ readonly Icon: IconComponent;
5
+ readonly token: string;
6
+ }
7
+ /** Resolves an entry kind to its prototype drawing + semantic token. */
8
+ export declare function visualForKind(kind: string): KindVisual;
9
+ export {};
10
+ //# sourceMappingURL=fileRowIcons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileRowIcons.d.ts","sourceRoot":"","sources":["../src/fileRowIcons.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAwD1C,KAAK,aAAa,GAAG,MAAM,YAAY,CAAC;AAExC,UAAU,UAAU;IAClB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAoBD,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAEtD"}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ---------------------------------------------------------------------------
3
+ // Per-kind icons — the exact drawings of the VAULT v9 prototype
4
+ // (vault-standalone.html): folder, dossier doc, workflow board, audit board,
5
+ // world view. Kind families map onto the prototype's distinct drawings; the
6
+ // token (not a hardcoded color) supplies the per-kind hue.
7
+ // ---------------------------------------------------------------------------
8
+ const FolderIcon = () => (_jsxs("svg", { width: 30, height: 26, viewBox: "0 0 48 42", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M3 6h14l4 5h24v25H3z", stroke: "currentColor", strokeWidth: 2, strokeLinejoin: "round" }), _jsx("path", { d: "M3 16h42", stroke: "currentColor", strokeWidth: 2 })] }));
9
+ const DocIcon = () => (_jsxs("svg", { width: 26, height: 30, viewBox: "0 0 28 32", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M4 2h13l7 7v21H4z", stroke: "currentColor", strokeWidth: 2, strokeLinejoin: "round" }), _jsx("path", { d: "M17 2v7h7M8 14h10M8 19h10M8 24h6", stroke: "currentColor", strokeWidth: 1.6 })] }));
10
+ const WorkflowBoardIcon = () => (_jsxs("svg", { width: 28, height: 28, viewBox: "0 0 30 30", fill: "none", "aria-hidden": "true", children: [_jsx("rect", { x: 2, y: 2, width: 9, height: 9, rx: 1.5, stroke: "currentColor", strokeWidth: 2 }), _jsx("rect", { x: 19, y: 11, width: 9, height: 9, rx: 1.5, stroke: "currentColor", strokeWidth: 2 }), _jsx("rect", { x: 2, y: 19, width: 9, height: 9, rx: 1.5, stroke: "currentColor", strokeWidth: 2 }), _jsx("path", { d: "M11 6.5h5.5A2.5 2.5 0 0 1 19 9v2M19 20.5h-5.5A2.5 2.5 0 0 1 11 18v-5", stroke: "currentColor", strokeWidth: 2 })] }));
11
+ const AuditBoardIcon = () => (_jsxs("svg", { width: 28, height: 28, viewBox: "0 0 30 30", fill: "none", "aria-hidden": "true", children: [_jsx("rect", { x: 2, y: 2, width: 9, height: 9, rx: 1.5, stroke: "currentColor", strokeWidth: 2 }), _jsx("rect", { x: 19, y: 2, width: 9, height: 9, rx: 1.5, stroke: "currentColor", strokeWidth: 2 }), _jsx("rect", { x: 10, y: 19, width: 9, height: 9, rx: 1.5, stroke: "currentColor", strokeWidth: 2 }), _jsx("path", { d: "M11 11v3a2 2 0 0 0 2 2h3M15 7h4M7 11v8", stroke: "currentColor", strokeWidth: 2 })] }));
12
+ const WorldIcon = () => (_jsxs("svg", { width: 28, height: 28, viewBox: "0 0 30 30", fill: "none", "aria-hidden": "true", children: [_jsx("circle", { cx: 15, cy: 15, r: 12, stroke: "currentColor", strokeWidth: 2 }), _jsx("path", { d: "M3 15h24M15 3c4 4 4 20 0 24M15 3c-4 4-4 20 0 24", stroke: "currentColor", strokeWidth: 1.6 })] }));
13
+ /**
14
+ * Kind → distinct prototype drawing + semantic token. The prototype palette:
15
+ * folder blue(accent), doc mint(ok), board amber(warn), world purple(signal).
16
+ */
17
+ const KIND_VISUALS = {
18
+ folder: { Icon: FolderIcon, token: "accent" },
19
+ dossier: { Icon: DocIcon, token: "ok" },
20
+ evidence: { Icon: DocIcon, token: "ok" },
21
+ reference: { Icon: DocIcon, token: "ok" },
22
+ board: { Icon: WorkflowBoardIcon, token: "warn" },
23
+ "workflow-board": { Icon: WorkflowBoardIcon, token: "warn" },
24
+ "audit-board": { Icon: AuditBoardIcon, token: "warn" },
25
+ world: { Icon: WorldIcon, token: "signal" },
26
+ };
27
+ /** Unknown kinds render the generic doc drawing under the accent token. */
28
+ const FALLBACK_VISUAL = { Icon: DocIcon, token: "accent" };
29
+ /** Resolves an entry kind to its prototype drawing + semantic token. */
30
+ export function visualForKind(kind) {
31
+ return KIND_VISUALS[kind] ?? FALLBACK_VISUAL;
32
+ }
@@ -0,0 +1,57 @@
1
+ import type { FileEntrySummary } from "@zephytiju/lattice-common-interfaces";
2
+ import type { RecentWorkStrings } from "./locales/index.js";
3
+ /**
4
+ * Workspace sync state of a recent file — drives the row's live dot:
5
+ * "live" renders the bright ok token (the prototype's mint dot), "syncing"
6
+ * the warn token, "synced" the dimmed ok token.
7
+ */
8
+ export type FileSyncState = "live" | "syncing" | "synced";
9
+ /**
10
+ * One recent IFileEntry record: the bounded Lattice file-entry summary
11
+ * (id, name, kind, sizeBytes) plus exactly the recency data the Recent Work
12
+ * design specifies — the updatedAt instant the section orders and relativizes
13
+ * ("EDITED 4M AGO"), the workspace sync state driving the live dot, and the
14
+ * two optional descriptor metadata parts ("LATTICE", "8 BLOCKS").
15
+ */
16
+ export interface RecentFileEntry extends FileEntrySummary {
17
+ /** ISO-8601 instant of the last edit — rows order by this, descending. */
18
+ readonly updatedAt: string;
19
+ /** Workspace sync state driving the row's live dot (default "live"). */
20
+ readonly sync?: FileSyncState;
21
+ /** Leading metadata status for the descriptor line (e.g. "LATTICE"); replaces the edited-ago part when present. */
22
+ readonly status?: string;
23
+ /** Trailing metadata detail for the descriptor line (e.g. "8 BLOCKS"). */
24
+ readonly detail?: string;
25
+ }
26
+ /** The recent-file projection the component consumes: entries ordered by recency by their publisher. */
27
+ export interface RecentFilesProjection {
28
+ readonly entries: readonly RecentFileEntry[];
29
+ }
30
+ /**
31
+ * Payload published on "recent-work.opened-file" when a row is opened — a
32
+ * bounded file reference pinned to the IFileEntry interface. This is the
33
+ * navigation/open INTENT (decision D8): the shell and the File Viewer's path
34
+ * slot receive the identifier and interpret it in their own way (D6);
35
+ * receivers fetch their own domain data (D4). Opening a recent file is NOT
36
+ * audit-worthy.
37
+ */
38
+ export type OpenedFile = {
39
+ readonly id: string;
40
+ readonly name: string;
41
+ readonly kind: string;
42
+ readonly sync: FileSyncState;
43
+ readonly ontologyInterface: "IFileEntry";
44
+ };
45
+ export interface FileRowProps {
46
+ /** The recent file entry the row renders. */
47
+ readonly entry: RecentFileEntry;
48
+ /** Locale-resolved string table (supplied by RecentWork or the host). */
49
+ readonly strings: RecentWorkStrings;
50
+ /** Supplies the full descriptor line (defaults to kind • status-or-editedAgo • detail). */
51
+ readonly descriptor?: (entry: RecentFileEntry) => string;
52
+ /** Renders the + insert affordance (insert contexts only; default off). */
53
+ readonly insertAffordance?: boolean;
54
+ /** Label for the insert affordance (defaults to the locale's `insert`). */
55
+ readonly insertLabel?: string;
56
+ }
57
+ //# sourceMappingURL=fileRowTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileRowTypes.d.ts","sourceRoot":"","sources":["../src/fileRowTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,WAAW,eAAgB,SAAQ,gBAAgB;IACvD,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC;IAC9B,mHAAmH;IACnH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wGAAwG;AACxG,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;CAC9C;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAC;CAC1C,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,2FAA2F;IAC3F,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,CAAC;IACzD,2EAA2E;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export { RecentWork } from "./RecentWork.js";
2
+ export type { RecentWorkProps } from "./RecentWork.js";
3
+ export { FileRow } from "./FileRow.js";
4
+ export type { FileRowProps, FileSyncState, RecentFileEntry, RecentFilesProjection, OpenedFile, } from "./FileRow.js";
5
+ export { en, zhCN, locales, stringsForLocale, formatMessage } from "./locales/index.js";
6
+ export type { RecentWorkLocale, RecentWorkStrings } from "./locales/index.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { RecentWork } from "./RecentWork.js";
2
+ export { FileRow } from "./FileRow.js";
3
+ export { en, zhCN, locales, stringsForLocale, formatMessage } from "./locales/index.js";
@@ -0,0 +1,25 @@
1
+ {
2
+ "recent-work": {
3
+ "sectionTitle": "RECENT WORK",
4
+ "sectionMeta": "CONTINUE WHERE YOU LEFT OFF",
5
+ "emptyTitle": "No recent work yet",
6
+ "emptyHint": "Files you open and edit will appear here so you can pick up where you left off.",
7
+ "openFile": "Open file",
8
+ "editedAgo": "EDITED {ago} AGO",
9
+ "minutesAgo": "{count}M",
10
+ "hoursAgo": "{count}H",
11
+ "daysAgo": "{count}D",
12
+ "syncLive": "Live",
13
+ "syncSyncing": "Syncing",
14
+ "syncSynced": "Synced",
15
+ "insert": "+",
16
+ "kindFolder": "FOLDER",
17
+ "kindDossier": "DOSSIER FILE",
18
+ "kindEvidence": "EVIDENCE FILE",
19
+ "kindReference": "REFERENCE",
20
+ "kindBoard": "BOARD",
21
+ "kindWorkflowBoard": "WORKFLOW BOARD",
22
+ "kindAuditBoard": "AUDIT BOARD",
23
+ "kindWorld": "GEOVISION FILE"
24
+ }
25
+ }
@@ -0,0 +1,81 @@
1
+ import enBundle from "./en.json";
2
+ /**
3
+ * Locales the recent-work micro-UI bundles. Every component-fixed UI string
4
+ * is resolved from these bundles (statically imported, resolveJsonModule); the
5
+ * component never hardcodes copy. Configuration-provided strings (an explicit
6
+ * sectionTitle override, …) stay composition-authored and are localized by the
7
+ * composer.
8
+ */
9
+ export type RecentWorkLocale = "en" | "zh-CN";
10
+ /**
11
+ * The per-locale string table — the keys of the en bundle. Declared widened
12
+ * (string values) so the zh-CN bundle is checked for exact key parity at
13
+ * compile time while keeping literal JSON types out of the public surface.
14
+ */
15
+ export type RecentWorkStrings = {
16
+ readonly [K in keyof typeof enBundle["recent-work"]]: string;
17
+ };
18
+ /** Parsed en locale bundle (namespaced under the component id "recent-work"). */
19
+ export declare const en: {
20
+ "recent-work": {
21
+ sectionTitle: string;
22
+ sectionMeta: string;
23
+ emptyTitle: string;
24
+ emptyHint: string;
25
+ openFile: string;
26
+ editedAgo: string;
27
+ minutesAgo: string;
28
+ hoursAgo: string;
29
+ daysAgo: string;
30
+ syncLive: string;
31
+ syncSyncing: string;
32
+ syncSynced: string;
33
+ insert: string;
34
+ kindFolder: string;
35
+ kindDossier: string;
36
+ kindEvidence: string;
37
+ kindReference: string;
38
+ kindBoard: string;
39
+ kindWorkflowBoard: string;
40
+ kindAuditBoard: string;
41
+ kindWorld: string;
42
+ };
43
+ };
44
+ /** Parsed zh-CN locale bundle (namespaced under the component id "recent-work"). */
45
+ export declare const zhCN: {
46
+ "recent-work": {
47
+ sectionTitle: string;
48
+ sectionMeta: string;
49
+ emptyTitle: string;
50
+ emptyHint: string;
51
+ openFile: string;
52
+ editedAgo: string;
53
+ minutesAgo: string;
54
+ hoursAgo: string;
55
+ daysAgo: string;
56
+ syncLive: string;
57
+ syncSyncing: string;
58
+ syncSynced: string;
59
+ insert: string;
60
+ kindFolder: string;
61
+ kindDossier: string;
62
+ kindEvidence: string;
63
+ kindReference: string;
64
+ kindBoard: string;
65
+ kindWorkflowBoard: string;
66
+ kindAuditBoard: string;
67
+ kindWorld: string;
68
+ };
69
+ };
70
+ /** All bundled locale bundles keyed by locale id — for downstream deep-merge. */
71
+ export declare const locales: Record<RecentWorkLocale, {
72
+ readonly "recent-work": RecentWorkStrings;
73
+ }>;
74
+ /** Resolves the string table for a locale. */
75
+ export declare function stringsForLocale(locale: RecentWorkLocale): RecentWorkStrings;
76
+ /**
77
+ * Simple `{placeholder}` interpolation (e.g. `"EDITED {ago} AGO"` + ago).
78
+ * Placeholder-for-value substitution only — no ICU, no regexes.
79
+ */
80
+ export declare function formatMessage(template: string, values: Readonly<Record<string, string | number>>): string;
81
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/locales/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,WAAW,CAAC;AAGjC;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC;AAE9C;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,CAAC,IAAI,MAAM,OAAO,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM;CAC7D,CAAC;AAOF,iFAAiF;AACjF,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAAW,CAAC;AAE3B,oFAAoF;AACpF,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;CAAa,CAAC;AAE/B,iFAAiF;AACjF,eAAO,MAAM,OAAO;4BAZ8C,iBAAiB;EAYrD,CAAC;AAE/B,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,CAE5E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,GAChD,MAAM,CAMR"}
@@ -0,0 +1,27 @@
1
+ import enBundle from "./en.json";
2
+ import zhCNBundle from "./zh-CN.json";
3
+ const bundles = {
4
+ en: enBundle,
5
+ "zh-CN": zhCNBundle,
6
+ };
7
+ /** Parsed en locale bundle (namespaced under the component id "recent-work"). */
8
+ export const en = enBundle;
9
+ /** Parsed zh-CN locale bundle (namespaced under the component id "recent-work"). */
10
+ export const zhCN = zhCNBundle;
11
+ /** All bundled locale bundles keyed by locale id — for downstream deep-merge. */
12
+ export const locales = bundles;
13
+ /** Resolves the string table for a locale. */
14
+ export function stringsForLocale(locale) {
15
+ return bundles[locale]["recent-work"];
16
+ }
17
+ /**
18
+ * Simple `{placeholder}` interpolation (e.g. `"EDITED {ago} AGO"` + ago).
19
+ * Placeholder-for-value substitution only — no ICU, no regexes.
20
+ */
21
+ export function formatMessage(template, values) {
22
+ let result = template;
23
+ for (const [key, value] of Object.entries(values)) {
24
+ result = result.replaceAll(`{${key}}`, String(value));
25
+ }
26
+ return result;
27
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "recent-work": {
3
+ "sectionTitle": "最近工作",
4
+ "sectionMeta": "从上次中断处继续",
5
+ "emptyTitle": "暂无最近工作",
6
+ "emptyHint": "打开和编辑过的文件会出现在这里,便于快速继续上次的工作。",
7
+ "openFile": "打开文件",
8
+ "editedAgo": "编辑于 {ago}前",
9
+ "minutesAgo": "{count}分钟",
10
+ "hoursAgo": "{count}小时",
11
+ "daysAgo": "{count}天",
12
+ "syncLive": "实时",
13
+ "syncSyncing": "同步中",
14
+ "syncSynced": "已同步",
15
+ "insert": "+",
16
+ "kindFolder": "文件夹",
17
+ "kindDossier": "案卷文件",
18
+ "kindEvidence": "证据文件",
19
+ "kindReference": "参考资料",
20
+ "kindBoard": "看板",
21
+ "kindWorkflowBoard": "工作流板",
22
+ "kindAuditBoard": "审计板",
23
+ "kindWorld": "GEOVISION 文件"
24
+ }
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "recent-work": {
3
+ "sectionTitle": "RECENT WORK",
4
+ "sectionMeta": "CONTINUE WHERE YOU LEFT OFF",
5
+ "emptyTitle": "No recent work yet",
6
+ "emptyHint": "Files you open and edit will appear here so you can pick up where you left off.",
7
+ "openFile": "Open file",
8
+ "editedAgo": "EDITED {ago} AGO",
9
+ "minutesAgo": "{count}M",
10
+ "hoursAgo": "{count}H",
11
+ "daysAgo": "{count}D",
12
+ "syncLive": "Live",
13
+ "syncSyncing": "Syncing",
14
+ "syncSynced": "Synced",
15
+ "insert": "+",
16
+ "kindFolder": "FOLDER",
17
+ "kindDossier": "DOSSIER FILE",
18
+ "kindEvidence": "EVIDENCE FILE",
19
+ "kindReference": "REFERENCE",
20
+ "kindBoard": "BOARD",
21
+ "kindWorkflowBoard": "WORKFLOW BOARD",
22
+ "kindAuditBoard": "AUDIT BOARD",
23
+ "kindWorld": "GEOVISION FILE"
24
+ }
25
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "recent-work": {
3
+ "sectionTitle": "最近工作",
4
+ "sectionMeta": "从上次中断处继续",
5
+ "emptyTitle": "暂无最近工作",
6
+ "emptyHint": "打开和编辑过的文件会出现在这里,便于快速继续上次的工作。",
7
+ "openFile": "打开文件",
8
+ "editedAgo": "编辑于 {ago}前",
9
+ "minutesAgo": "{count}分钟",
10
+ "hoursAgo": "{count}小时",
11
+ "daysAgo": "{count}天",
12
+ "syncLive": "实时",
13
+ "syncSyncing": "同步中",
14
+ "syncSynced": "已同步",
15
+ "insert": "+",
16
+ "kindFolder": "文件夹",
17
+ "kindDossier": "案卷文件",
18
+ "kindEvidence": "证据文件",
19
+ "kindReference": "参考资料",
20
+ "kindBoard": "看板",
21
+ "kindWorkflowBoard": "工作流板",
22
+ "kindAuditBoard": "审计板",
23
+ "kindWorld": "GEOVISION 文件"
24
+ }
25
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@zephytiju/prism-recent-work",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Platform Prism recent-work micro-UI (component id \"recent-work\"): the VAULT root-level continuation strip — RECENT WORK / CONTINUE WHERE YOU LEFT OFF header plus per-prototype file-row members (kind icon, name, descriptor, live sync dot) rendering recent IFileEntry records 0..N at app runtime, ordered by updatedAt with an optional recency window and a configurable column grid. Consumes the recent-work.file-entries channel; opening a row publishes the navigation/open intent bounded payload for the shell and the File Viewer's path slot. No Lattice calls. Ships en + zh-CN locale bundles.",
6
+ "license": "UNLICENSED",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./locales/*": "./locales/*"
15
+ },
16
+ "publishConfig": { "access": "public" },
17
+ "files": ["dist", "locales"],
18
+ "scripts": {
19
+ "dev": "vite --host 127.0.0.1",
20
+ "build": "tsc -p tsconfig.build.json && node scripts/copy-locales.mjs",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "shot-demo": "node scripts/shot-demo.mjs"
24
+ },
25
+ "peerDependencies": {
26
+ "@mantine/core": "^8.1.1",
27
+ "@zephytiju/lattice-common-interfaces": ">=0.1.0",
28
+ "@zephytiju/prism-react": ">=0.1.0",
29
+ "react": "^19.1.0",
30
+ "react-dom": "^19.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@mantine/core": "8.1.1",
34
+ "@testing-library/dom": "^10.4.0",
35
+ "@testing-library/react": "^16.1.0",
36
+ "@types/node": "^22.10.2",
37
+ "@types/react": "^19.1.0",
38
+ "@types/react-dom": "^19.1.0",
39
+ "@zephytiju/lattice-common-interfaces": "0.1.0",
40
+ "@zephytiju/prism-react": "0.1.0",
41
+ "jsdom": "^25.0.1",
42
+ "puppeteer-core": "^24.10.0",
43
+ "react": "19.1.0",
44
+ "react-dom": "19.1.0",
45
+ "typescript": "^5.7.2",
46
+ "vite": "6.0.5",
47
+ "vitest": "^2.1.8"
48
+ },
49
+ "engines": {
50
+ "node": ">=20"
51
+ }
52
+ }