@zephytiju/prism-main-editor 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.
Files changed (69) hide show
  1. package/README.md +252 -0
  2. package/dist/CommentsPane.d.ts +32 -0
  3. package/dist/CommentsPane.d.ts.map +1 -0
  4. package/dist/CommentsPane.js +85 -0
  5. package/dist/MainEditor.d.ts +59 -0
  6. package/dist/MainEditor.d.ts.map +1 -0
  7. package/dist/MainEditor.js +102 -0
  8. package/dist/TitleBar.d.ts +28 -0
  9. package/dist/TitleBar.d.ts.map +1 -0
  10. package/dist/TitleBar.js +55 -0
  11. package/dist/blocks/AssessmentBlockView.d.ts +21 -0
  12. package/dist/blocks/AssessmentBlockView.d.ts.map +1 -0
  13. package/dist/blocks/AssessmentBlockView.js +34 -0
  14. package/dist/blocks/BlockRow.d.ts +33 -0
  15. package/dist/blocks/BlockRow.d.ts.map +1 -0
  16. package/dist/blocks/BlockRow.js +56 -0
  17. package/dist/blocks/BlockStack.d.ts +27 -0
  18. package/dist/blocks/BlockStack.d.ts.map +1 -0
  19. package/dist/blocks/BlockStack.js +75 -0
  20. package/dist/blocks/DragMenu.d.ts +22 -0
  21. package/dist/blocks/DragMenu.d.ts.map +1 -0
  22. package/dist/blocks/DragMenu.js +219 -0
  23. package/dist/blocks/EmbeddedBlockChrome.d.ts +34 -0
  24. package/dist/blocks/EmbeddedBlockChrome.d.ts.map +1 -0
  25. package/dist/blocks/EmbeddedBlockChrome.js +81 -0
  26. package/dist/blocks/EvidenceTableView.d.ts +25 -0
  27. package/dist/blocks/EvidenceTableView.d.ts.map +1 -0
  28. package/dist/blocks/EvidenceTableView.js +30 -0
  29. package/dist/blocks/GeovisionEmbedView.d.ts +28 -0
  30. package/dist/blocks/GeovisionEmbedView.d.ts.map +1 -0
  31. package/dist/blocks/GeovisionEmbedView.js +33 -0
  32. package/dist/blocks/InsertMenu.d.ts +22 -0
  33. package/dist/blocks/InsertMenu.d.ts.map +1 -0
  34. package/dist/blocks/InsertMenu.js +59 -0
  35. package/dist/blocks/InsertRail.d.ts +28 -0
  36. package/dist/blocks/InsertRail.d.ts.map +1 -0
  37. package/dist/blocks/InsertRail.js +29 -0
  38. package/dist/blocks/NarrativeBlockView.d.ts +22 -0
  39. package/dist/blocks/NarrativeBlockView.d.ts.map +1 -0
  40. package/dist/blocks/NarrativeBlockView.js +44 -0
  41. package/dist/blocks/NativeTableView.d.ts +12 -0
  42. package/dist/blocks/NativeTableView.d.ts.map +1 -0
  43. package/dist/blocks/NativeTableView.js +36 -0
  44. package/dist/blocks/SavedViewDropdown.d.ts +17 -0
  45. package/dist/blocks/SavedViewDropdown.d.ts.map +1 -0
  46. package/dist/blocks/SavedViewDropdown.js +85 -0
  47. package/dist/blocks/SelectionToolbar.d.ts +25 -0
  48. package/dist/blocks/SelectionToolbar.d.ts.map +1 -0
  49. package/dist/blocks/SelectionToolbar.js +175 -0
  50. package/dist/blocks/menuStyles.d.ts +32 -0
  51. package/dist/blocks/menuStyles.d.ts.map +1 -0
  52. package/dist/blocks/menuStyles.js +32 -0
  53. package/dist/index.d.ts +26 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +11 -0
  56. package/dist/locales/en.json +37 -0
  57. package/dist/locales/index.d.ts +105 -0
  58. package/dist/locales/index.d.ts.map +1 -0
  59. package/dist/locales/index.js +27 -0
  60. package/dist/locales/zh-CN.json +37 -0
  61. package/dist/tokens.d.ts +42 -0
  62. package/dist/tokens.d.ts.map +1 -0
  63. package/dist/tokens.js +43 -0
  64. package/dist/types.d.ts +207 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +1 -0
  67. package/locales/en.json +37 -0
  68. package/locales/zh-CN.json +37 -0
  69. package/package.json +79 -0
package/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # PrismMainEditorMicroUI
2
+
3
+ Platform Prism main-editor micro-UI. Component id: `main-editor`.
4
+ Published to npm as [`@zephytiju/prism-main-editor`](https://www.npmjs.com/package/@zephytiju/prism-main-editor).
5
+
6
+ The dossier editor axiom component (design decision D9): the single editor component owning the
7
+ title bar — breadcrumb + autosave stamp, `DRAFT / V07` state chip, `COMMENTS` toggle (active state
8
+ amber) driving the in-editor comments pane, `SHARE`, `•••` overflow — absorbed from the retired
9
+ Title Bar component, plus the typed block stack with `+` / drag insert rows and the expandable
10
+ block-anchored comments pane. Block surfaces follow the D10 taxonomy: narrative,
11
+ analyst-judgment, and dossier-native table blocks render as plain document elements — no outlining
12
+ box, no card chrome, no kind pill; the analyst-judgment highlight is an accent bar + tint only;
13
+ source links are ordinary inline hyperlinks embedded inside paragraph text (accent-colored,
14
+ underlined, preceded by the ↗ glyph) — never standalone link blocks; native tables render with row
15
+ rules like Feishu native tables. Only file-preview blocks are embedded, and they HOST the REAL
16
+ published micro-UI packages — no synthetic editor-side previews exist:
17
+
18
+ - the **geovision-embed** block hosts [`@zephytiju/prism-geovision-embed-block`](https://www.npmjs.com/package/@zephytiju/prism-geovision-embed-block)
19
+ (>= 0.2.0), which renders the full D10 chrome (kind tag `GEOVISION`, `SRC FILE · {file}` chip,
20
+ `VIEW ▾` variant dropdown fed by the render package's `describeViews(sourceFileRef)` through the
21
+ host Lattice transport, ↗ open arrow) and itself hosts the published render dispatcher
22
+ (`@zephytiju/prism-geovision-embed` 0.2.0) in its body slot, rendering exactly the ONE variant
23
+ the current viewId (`${fileRef}#${previewKind}`) selects;
24
+ - the **evidence-table** block hosts [`@zephytiju/prism-evidence-table`](https://www.npmjs.com/package/@zephytiju/prism-evidence-table)
25
+ (>= 0.1.0), which renders its own card chrome and the previewed saved view's
26
+ `IEvidenceSource`-shaped rows (source / observation / confidence / audit locator).
27
+
28
+ All data arrives IDossierDoc-shaped over the component channels (or explicit props); add, reorder,
29
+ and remove are dossier ACTIONS the editor emits as events for the host to apply. Composed
30
+ applications (for example Guanlan) consume it as-is; the component is platform-owned.
31
+
32
+ ## Configuration keys
33
+
34
+ | Prop | Meaning |
35
+ | --- | --- |
36
+ | `locale` | UI locale for the component-fixed strings: `"en" \| "zh-CN"` (default `"en"`) — see [i18n](#internationalization-i18n) |
37
+ | `variant` | `"editor"` (default; insert + drag controls) or `"readonly"` (published view — controls hidden) |
38
+ | `enabledBlockKinds` | Block kinds the insert menu offers (default: all five — `narrative`, `assessment`, `geovision-embed`, `evidence-table`, `native-table`) |
39
+ | `showInsertControls` | Whether the `+` insert controls render in the editor variant (default `true`; drag handles follow `variant`) |
40
+ | `titleFormat` | `(dossier) => string` workspace title line (default: the dossier `title`) |
41
+ | `titleBarActions` | Title-bar actions to enable: `{ comments?, share?, overflow? }` (all default `true`; the title bar is component configuration — Prism has no composed title node) |
42
+ | `dossier` | Explicit `MainEditorDossier` override; defaults to the `main-editor.dossier` channel |
43
+ | `commentThreads` | Explicit threads override; defaults to the `main-editor.comment-threads` channel |
44
+ | `height` | Editor surface height in px (default `900`) |
45
+
46
+ ## Channel contract
47
+
48
+ | Direction | Kind | Id | Payload |
49
+ | --- | --- | --- | --- |
50
+ | consumes | state | `main-editor.dossier` | `MainEditorDossier \| null` — IDossierDoc-shaped (`id`, `title`, `blocks` of `DossierDocBlock`) widened with breadcrumb, doc code, autosave stamp, draft state, version, doc meta, and per-kind block content |
51
+ | consumes | state | `main-editor.comment-threads` | `readonly MainEditorCommentThread[] \| null` — ICommentThread-shaped comments widened with `blockId` anchoring and open/done status |
52
+ | consumes | state | `main-editor.comments-open` | `boolean` — the pane state channel (D6); `null` defaults to the prototype's open pane |
53
+ | consumes | state | `main-editor.focused-block` | `{ blockId } \| null` — derived for the focus wash |
54
+ | publishes | state | `main-editor.comments-open` | `boolean` — the title-bar COMMENTS toggle (setter-only publish; amber-active when open) |
55
+ | publishes | state | `main-editor.focused-block` | `{ blockId }` — clicking a block or a thread card (block review mode) |
56
+ | emits | event | `main-editor.insert-block-requested` | `{ afterBlockId: string \| null, kind }` — the `+` block-kind menu; a dossier action (IDossierDoc) the host applies |
57
+ | emits | event | `main-editor.reorder-block-requested` | `{ blockId, direction: "up" \| "down" }` — the ⠿ drag menu; a dossier action |
58
+ | emits | event | `main-editor.remove-block-requested` | `{ blockId }` — the ⠿ drag menu; a dossier action |
59
+ | emits | event | `main-editor.open-view-requested` | `{ sourceFileRef, viewId }` — the ↗ open intents of the HOSTED embedded blocks, re-emitted so hosts keep ONE open-intent contract for every embedded block |
60
+ | emits | event | `main-editor.share-requested` | `{ docId }` — the SHARE sharing intent (never grants authorization; the host performs the share and records the IAuditChain entry) |
61
+
62
+ The variant state of the embedded blocks is owned by the HOSTED components through their own
63
+ file-level channels — `geovision-embed.selected-view` (`{ sourceFileRef, viewId }`, view ids
64
+ `${fileRef}#${previewKind}`) and `evidence-table.selected-view` (`{ sourceFileRef, viewId }`) —
65
+ so every embed of the same source file re-previews the picked variant (D10). The editor's former
66
+ per-block `main-editor.selected-view` channel was REMOVED in the real-components migration: it
67
+ conflicted with the components' file-level semantics; the editor no longer reimplements embedded
68
+ view selection at all.
69
+
70
+ Channel ids are string literals at every call-site so the build-time channel-graph scanner can
71
+ derive the graph. Channel reads use `usePrismStateValue`; every publication is setter-only (or an
72
+ event), so publishers never re-render from their own writes. Block focus follows the EntityCard
73
+ pattern — each item owns its binding, publishes on change, and derives its current value from the
74
+ same channel, so every host bound to the channel stays in sync (D6 state passing).
75
+
76
+ ## Audit rule
77
+
78
+ Routine dossier reads and interactions are NOT audit-worthy: this component emits NO audit event
79
+ for them. The only audit-adjacent emission is the `main-editor.share-requested` intent — sharing is
80
+ the audit-worthy action, and the recording host (not this component) appends the IAuditChain entry.
81
+
82
+ ## Internationalization (i18n)
83
+
84
+ The component ships `en` and `zh-CN` locale bundles — `src/locales/en.json` / `src/locales/zh-CN.json` —
85
+ and every component-fixed UI string is resolved from them (the `DOSSIER` doc prefix, `AUTOSAVED {time}`,
86
+ the `COMMENTS · {count}` toggle and `{count} OPEN COMMENTS` chip lines, SHARE, the `INSERT BLOCK` /
87
+ `BLOCK ACTIONS` menus, every block-kind label, the comments pane copy, and the
88
+ empty state). The embedded-block chrome copy (kind tags, `SRC FILE · {file}` chips,
89
+ `VIEW · {variant}` pills, `SAVED VIEWS · {file}` menus, ↗ arrow titles) lives in the HOSTED
90
+ packages' own locale bundles and localizes through the `locale` prop the editor forwards. The
91
+ component renders no hardcoded copy.
92
+
93
+ ```json
94
+ {
95
+ "main-editor": {
96
+ "docPrefix": "DOSSIER",
97
+ "autosavedAt": "AUTOSAVED {time}",
98
+ "commentsButtonWithCount": "COMMENTS · {count}",
99
+ "openCommentsCount": "{count} OPEN COMMENTS",
100
+ "share": "SHARE",
101
+ "kindAssessment": "ANALYST JUDGMENT",
102
+ "openThreads": "{count} OPEN",
103
+ "threadBlockMeta": "BLOCK {block} • {status}"
104
+ }
105
+ }
106
+ ```
107
+
108
+ - `locale?: "en" | "zh-CN"` prop (default `"en"`) selects the string table per instance.
109
+ - `{placeholder}` templates (`{count}`, `{time}`, `{file}`, `{variant}`, `{block}`, `{status}`) use
110
+ plain substitution via `formatMessage`, exported from the package entry.
111
+ - **Composition-authored strings are localized by the composer; component-fixed strings live in the
112
+ locale JSONs.** Breadcrumb segments, the doc meta line, block content, saved-view labels, and
113
+ thread titles are data (IDossierDoc/ICommentThread-shaped), so a localized dossier supplies its
114
+ own translated content per locale.
115
+ - Locale bundles are namespaced under the component id (`"main-editor"`) so a composer can
116
+ deep-merge every component's bundle into ONE UI language bundle without collisions:
117
+
118
+ ```ts
119
+ import { locales as mainEditorLocales } from "@zephytiju/prism-main-editor";
120
+ // mainEditorLocales["zh-CN"] -> { "main-editor": { … } }
121
+ const uiBundle = deepMerge(hostStrings, mainEditorLocales["zh-CN"]);
122
+ ```
123
+
124
+ The parsed bundles are exported from the package entry (`locales`, `en`, `zhCN`,
125
+ `stringsForLocale`), and the raw JSONs are also served by the `./locales/*` exports subpath
126
+ (e.g. `@zephytiju/prism-main-editor/locales/zh-CN.json`); `files` ships both `dist` and `locales`.
127
+
128
+ ## Theme
129
+
130
+ No palette is hardcoded. Every color resolves to SEMANTIC theme tokens (`ok`, `accent`, `threat`,
131
+ `warn`, `signal`, `card`, `card-dark`, `input`, `border`, `line`, `text`, `muted`, `deep`, `panel`)
132
+ consumed as CSS variables, plus `--mantine-font-family-monospace` for the mono typography — the
133
+ palette is supplied entirely by the host's `MantineProvider`. The prototype's mint/blue/amber/red/
134
+ purple accents map onto `ok` / `accent` / `warn` / `threat` / `signal` respectively; translucent
135
+ surfaces (the amber highlight tint, the comments-pane backdrop, the focus wash) derive from the
136
+ same tokens through `color-mix`. The local demo ships TWO themes, both defined in `src/demo.tsx`:
137
+ `geovisionTheme` (dark), mapping each semantic token onto the exact `:root` variables of the v9
138
+ design prototype, and the contrasting `latticeLightTheme` (light), mapping the SAME semantic token
139
+ keys onto a different palette — the component is skinned purely through the surrounding
140
+ `MantineProvider`.
141
+
142
+ ## Source layout
143
+
144
+ `src/` is strictly two parts:
145
+
146
+ - Component source (what the package compiles): `MainEditor.tsx` (orchestrator — channel wiring,
147
+ empty state, layout), `TitleBar.tsx` (the absorbed title bar, D9), `CommentsPane.tsx` (the
148
+ expandable in-editor pane), `blocks/` (`BlockStack.tsx` — workspace header, insert rows, +/⠿
149
+ menus, per-kind dispatch; `NarrativeBlockView.tsx`, `AssessmentBlockView.tsx`,
150
+ `GeovisionEmbedView.tsx` — HOSTS the real `@zephytiju/prism-geovision-embed-block`;
151
+ `EvidenceTableView.tsx` — HOSTS the real `@zephytiju/prism-evidence-table`;
152
+ `NativeTableView.tsx` — the D10 block surfaces; `InsertMenu.tsx`, `InsertRail.tsx`,
153
+ `DragMenu.tsx`, `SelectionToolbar.tsx`, `menuStyles.ts` — the D11 affordances), `types.ts` (the
154
+ IDossierDoc/ICommentThread-shaped data model + event payloads), `tokens.ts`
155
+ (semantic-token shorthands), `index.ts` (public entry), and `src/locales/` (`en.json`,
156
+ `zh-CN.json`, `index.ts` — the i18n string bundles, their resolver, and the `{placeholder}`
157
+ interpolation helper). The embedded item templates are the hosting seams for the real published
158
+ packages — the editor renders NO synthetic embed previews.
159
+ - Demo: exactly ONE file, `src/demo.tsx` — the two host themes (GeoVision v9 + Lattice Light), all
160
+ demo test data (the Redwater mock dossier + threads mirroring the v9 prototype; the demo
161
+ geovision-view file built through the bundle's own `saveGeovisionView`; the evidence saved views
162
+ with their rows), the channel seeding (including the two file-level selected-view channels the
163
+ hosted components own), the mock Lattice action executor serving the demo file (the
164
+ standards-compliant host pattern — the hosted block's `describeViews()` and the REAL render
165
+ dispatcher run end to end against it), the mock dossier-action applier (insert/reorder/remove/
166
+ duplicate — the host side of the action contract), the channel monitors + action log, and the
167
+ demo page rendering TWO `MainEditor` instances behind a global EN | 中文 language switcher (plus
168
+ per-instance switches).
169
+
170
+ The npm package ships `dist` (compiled component + type declarations + locale JSONs) and the
171
+ top-level `locales/` directory (the raw JSON bundles, served by the `./locales/*` exports
172
+ subpath); no demo code is published. `scripts/copy-locales.mjs` copies the JSON bundles into both
173
+ locations during `npm run build`.
174
+
175
+ ## Dependencies and build order
176
+
177
+ The two embedded-block packages are **peerDependencies AND devDependencies**:
178
+
179
+ ```json
180
+ "peerDependencies": {
181
+ "@zephytiju/prism-evidence-table": ">=0.1.0",
182
+ "@zephytiju/prism-geovision-embed-block": ">=0.2.0",
183
+ "@zephytiju/prism-react": ">=0.2.0", …
184
+ }
185
+ ```
186
+
187
+ While both consumed packages are UNPUBLISHED local builds, the devDependencies resolve them as
188
+ `file:` links into the sibling checkouts — so the required BUILD ORDER is:
189
+
190
+ 1. `npm run build` in `../PrismGeovisionEmbedBlockMicroUI` (ships `dist` + `locales`),
191
+ 2. `npm run build` in `../PrismEvidenceTableMicroUI` (ships `dist` + `locales`),
192
+ 3. only then `npm install` / `npm run build` / `npm test` here — the `file:` links and the
193
+ `resolve.dedupe` in `vite.config.ts` / `vitest.config.ts` (react, react-dom, @mantine/core,
194
+ @zephytiju/prism-react, @zephytiju/prism-geovision-embed) keep the linked packages sharing ONE
195
+ prism-react channel store / action executor and ONE React instance with the editor.
196
+
197
+ The devDependencies additionally install the hosted render stack's registry peers
198
+ (`@zephytiju/prism-geovision-embed` 0.2.0 with its cesium/resium deps, `@zephytiju/prism-relation-fluxboard`,
199
+ `@zephytiju/prism-stats-panel`, `@zephytiju/prism-timeline-board`, `@xyflow/react`,
200
+ `@zephytiju/lattice-common-bundle`) so the demo and tests exercise the REAL dispatcher end to
201
+ end. `@zephytiju/prism-react` is pinned `>=0.2.0` because the hosted stack resolves the source
202
+ file through the 0.2.0 transport seam (`setPrismActionExecutor` / `useLatticeTransport`).
203
+
204
+ ## Local development
205
+
206
+ ```sh
207
+ npm install
208
+ npm run typecheck
209
+ npm test
210
+ npm run dev
211
+ npm run shot-demo
212
+ ```
213
+
214
+ `npm install` pulls the platform peers (`@zephytiju/prism-react`,
215
+ `@zephytiju/lattice-common-interfaces`) from the npm registry, along with the host-side peer
216
+ dependencies (`react`, `react-dom`, `@mantine/core`) and — through the `file:` links — the two
217
+ locally built embedded-block packages (see the build order above). When consuming the published
218
+ package, install it directly (`npm install @zephytiju/prism-main-editor`) and provide the peer
219
+ dependencies (including the two embedded-block packages once published) in the host application.
220
+
221
+ The demo (`npm run dev`, entry `src/demo.tsx`) plays the host: at module scope it installs the
222
+ mock Lattice action executor serving the demo geovision-view file, then seeds the Redwater mock
223
+ dossier, its threads, and the two file-level selected-view channels onto the GLOBAL channels, and
224
+ renders TWO `MainEditor` instances, each inside its own MantineProvider with a different theme
225
+ (GeoVision v9 dark, Lattice Light). The geovision-embed block row hosts the REAL
226
+ `@zephytiju/prism-geovision-embed-block`: its VIEW ▾ list comes from the REAL `describeViews()`
227
+ call over the demo executor (one saved view per previewKind — `${fileRef}#map / #relational /
228
+ #timeline / #stats`), and its body renders the REAL published dispatcher — exactly the ONE
229
+ variant the current viewId selects (the demo opts the map variant into `engine="svg"` for
230
+ deterministic headless capture). The evidence-table block row hosts the REAL
231
+ `@zephytiju/prism-evidence-table` with the demo saved views' rows. The COMMENTS toggle drives
232
+ the pane through the shared `main-editor.comments-open` channel, so toggling it in EITHER
233
+ instance opens the pane in BOTH. The `+` insert control opens the block-kind menu and emits
234
+ `insert-block-requested`; the ⠿ drag menu emits reorder/remove — the mock host applies these
235
+ dossier actions against its projection and republishes, exactly how a real host routes them
236
+ through IDossierDoc actions. The hosted blocks' ↗ arrows emit their open intents, which the
237
+ editor re-emits as `main-editor.open-view-requested`. The channel monitors and action log below
238
+ the instances show all of this live. `npm run shot-demo` boots the vite dev server, drives the
239
+ demo in headless Chrome, and captures the evidence states to `/tmp/guanlan-review/`:
240
+ `demo-main-editor-real-embeds-dark-en-v4.png`,
241
+ `demo-main-editor-real-embeds-variant-switch-v4.png`,
242
+ `demo-main-editor-real-embeds-light-zh-cn-v4.png`, and
243
+ `demo-main-editor-full-page.png`.
244
+
245
+ ## Design record
246
+
247
+ https://qcnwge0wy4s0.feishu.cn/wiki/B87YwS2PriaGvlkAcOtctKm8nsd — decisions D1–D10 (esp. D3
248
+ runtime blocks, D5 component tiers, D6 state passing, D8 channel contracts, D9 title-bar
249
+ absorption, D10 block surface taxonomy). Component doc:
250
+ https://qcnwge0wy4s0.feishu.cn/wiki/UqqYwbzkViRiprkNpkRcKt3tnFf. Visual reference: the
251
+ authoritative `dossier-editor-standalone-v9.html` prototype attached to the design doc
252
+ (`.ed-head` / `.nblock` / `.hl` / `.block` / `.vsel` / `.cpane`).
@@ -0,0 +1,32 @@
1
+ import type { MainEditorLocale } from "./locales/index.js";
2
+ import type { MainEditorCommentThread } from "./types.js";
3
+ export interface CommentsPaneProps {
4
+ readonly threads: readonly MainEditorCommentThread[];
5
+ readonly open: boolean;
6
+ readonly locale: MainEditorLocale;
7
+ /** Ordered block ids of the rendered stack — threads anchor by "BLOCK {ordinal}". */
8
+ readonly blockOrder: readonly string[];
9
+ /**
10
+ * D11 alignment: content-space top (px) of each rendered block row, measured
11
+ * by the editor — every thread card positions at its anchor block's top
12
+ * (minus the workspace scroll offset the rail mirrors).
13
+ */
14
+ readonly blockAnchors: Readonly<Record<string, number>>;
15
+ /** Current workspace scroll offset the rail mirrors (px). */
16
+ readonly scrollTop: number;
17
+ }
18
+ /**
19
+ * The block-anchored comments rail — expandable INSIDE the editor body (D6)
20
+ * and ALIGNED to the anchored blocks (D11): each card sits at the vertical
21
+ * position of its anchor block (the workspace's block-row tops minus the
22
+ * scroll offset the rail mirrors), and multiple threads on one block stack
23
+ * directly below each other — Feishu-style. The title-bar COMMENTS toggle
24
+ * drives it through the shared "main-editor.comments-open" channel state
25
+ * (D6), not a direct reference, so any peer bound to the same channel stays
26
+ * in sync. Closed, the pane slides out with the prototype's translateX(100%)
27
+ * transition; open-thread cards render amber; clicking a thread publishes
28
+ * "main-editor.focused-block". Thread entries are ICommentThread-shaped
29
+ * bounded comments.
30
+ */
31
+ export declare function CommentsPane({ threads, open, locale, blockOrder, blockAnchors, scrollTop, }: CommentsPaneProps): import("react").JSX.Element;
32
+ //# sourceMappingURL=CommentsPane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CommentsPane.d.ts","sourceRoot":"","sources":["../src/CommentsPane.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAA0B,MAAM,YAAY,CAAC;AAGlF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,IAAI,EACJ,MAAM,EACN,UAAU,EACV,YAAY,EACZ,SAAS,GACV,EAAE,iBAAiB,+BAsFnB"}
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Group, Stack, Text } from "@mantine/core";
3
+ import { usePrismStateSetter } from "@zephytiju/prism-react";
4
+ import { formatMessage, stringsForLocale } from "./locales/index.js";
5
+ import { BORDER, CARD, DEEP, MONO, MUTED, PANE_BACKDROP, WARN, pad2 } from "./tokens.js";
6
+ /** Thread-card geometry (px): fixed-height cards with a fixed stacking gap. */
7
+ const CARD_HEIGHT = 56;
8
+ const CARD_GAP = 8;
9
+ /** The pane-header height (px) the anchor tops compensate for. */
10
+ const HEADER_HEIGHT = 56;
11
+ /** Cards never slide under the pane header. */
12
+ const CARD_MIN_TOP = 8;
13
+ /**
14
+ * The block-anchored comments rail — expandable INSIDE the editor body (D6)
15
+ * and ALIGNED to the anchored blocks (D11): each card sits at the vertical
16
+ * position of its anchor block (the workspace's block-row tops minus the
17
+ * scroll offset the rail mirrors), and multiple threads on one block stack
18
+ * directly below each other — Feishu-style. The title-bar COMMENTS toggle
19
+ * drives it through the shared "main-editor.comments-open" channel state
20
+ * (D6), not a direct reference, so any peer bound to the same channel stays
21
+ * in sync. Closed, the pane slides out with the prototype's translateX(100%)
22
+ * transition; open-thread cards render amber; clicking a thread publishes
23
+ * "main-editor.focused-block". Thread entries are ICommentThread-shaped
24
+ * bounded comments.
25
+ */
26
+ export function CommentsPane({ threads, open, locale, blockOrder, blockAnchors, scrollTop, }) {
27
+ const strings = stringsForLocale(locale);
28
+ const publishFocusedBlock = usePrismStateSetter("main-editor.focused-block");
29
+ const openCount = threads.filter((thread) => thread.status === "open").length;
30
+ // Stack index per thread: its ordinal among the threads sharing the block.
31
+ const seen = new Map();
32
+ const stackIndex = threads.map((thread) => {
33
+ const index = seen.get(thread.blockId) ?? 0;
34
+ seen.set(thread.blockId, index + 1);
35
+ return index;
36
+ });
37
+ return (_jsxs(Box, { w: 340, style: {
38
+ position: "absolute",
39
+ right: 0,
40
+ top: 0,
41
+ bottom: 0,
42
+ background: PANE_BACKDROP,
43
+ borderLeft: `1px solid var(--mantine-color-line-filled)`,
44
+ transform: open ? "translateX(0)" : "translateX(100%)",
45
+ transition: "transform .25s ease",
46
+ zIndex: 5,
47
+ ...(open ? {} : { pointerEvents: "none" }),
48
+ }, "data-testid": "main-editor-comments-pane", "aria-hidden": !open, children: [_jsxs(Group, { justify: "space-between", align: "center", px: 14, h: 56, wrap: "nowrap", style: { background: CARD, borderBottom: `1px solid ${BORDER}` }, "data-testid": "main-editor-comments-head", children: [_jsxs(Stack, { gap: 3, children: [_jsx(Text, { fz: 12, fw: 600, c: "text", children: strings.commentsTitle }), _jsx(Text, { ff: MONO, fz: 7, style: { color: MUTED, letterSpacing: "0.05em" }, children: strings.commentsSubtitle })] }), _jsx(Text, { ff: MONO, fz: 7, fw: 500, c: "warn", "data-testid": "main-editor-comments-open-count", children: formatMessage(strings.openThreads, { count: openCount }) })] }), _jsx(Box, { style: { position: "relative", height: "calc(100% - 56px)", overflow: "hidden" }, children: threads.map((thread, index) => {
49
+ const anchorTop = blockAnchors[thread.blockId] ?? 0;
50
+ // The track begins below the pane header; compensating HEADER_HEIGHT
51
+ // puts the card at the anchor block's own viewport position.
52
+ const top = Math.max(anchorTop - HEADER_HEIGHT - scrollTop + stackIndex[index] * (CARD_HEIGHT + CARD_GAP), CARD_MIN_TOP);
53
+ return (_jsx(Box, { style: {
54
+ position: "absolute",
55
+ top,
56
+ left: 14,
57
+ right: 14,
58
+ height: CARD_HEIGHT,
59
+ transition: "top .12s ease",
60
+ }, "data-testid": `main-editor-thread-${thread.id}`, "data-anchor-block": thread.blockId, "data-stack-index": stackIndex[index], onClick: () => {
61
+ publishFocusedBlock({ blockId: thread.blockId });
62
+ }, children: _jsx(ThreadCard, { thread: thread, locale: locale, ordinal: blockOrder.indexOf(thread.blockId) + 1 }) }, thread.id));
63
+ }) })] }));
64
+ }
65
+ function ThreadCard({ thread, locale, ordinal, }) {
66
+ const strings = stringsForLocale(locale);
67
+ const status = thread.status === "open" ? strings.statusOpen : strings.statusDone;
68
+ return (_jsxs(Group, { gap: 12, align: "center", wrap: "nowrap", px: 10, h: CARD_HEIGHT, style: {
69
+ background: CARD,
70
+ border: `1px solid ${thread.status === "open" ? WARN : BORDER}`,
71
+ borderRadius: 6,
72
+ cursor: "pointer",
73
+ }, children: [_jsx(Box, { w: 28, h: 28, style: {
74
+ borderRadius: 14,
75
+ background: thread.status === "open" ? WARN : BORDER,
76
+ color: DEEP,
77
+ fontFamily: MONO,
78
+ fontSize: 9,
79
+ fontWeight: 500,
80
+ display: "flex",
81
+ alignItems: "center",
82
+ justifyContent: "center",
83
+ flexShrink: 0,
84
+ }, children: String(thread.comments.length).padStart(2, "0") }), _jsxs(Stack, { gap: 2, miw: 0, children: [_jsx(Text, { fz: 10, fw: 500, c: "text", style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: thread.quote === undefined ? thread.title : `“${thread.quote}”` }), _jsx(Text, { ff: MONO, fz: 7, style: { color: MUTED, letterSpacing: "0.02em" }, children: formatMessage(strings.threadBlockMeta, { block: pad2(ordinal), status }) })] })] }));
85
+ }
@@ -0,0 +1,59 @@
1
+ import type { MainEditorLocale } from "./locales/index.js";
2
+ import type { MainEditorBlockKind, MainEditorCommentThread, MainEditorDossier } from "./types.js";
3
+ export interface MainEditorProps {
4
+ /** Dossier projection override; defaults to the "main-editor.dossier" channel. */
5
+ readonly dossier?: MainEditorDossier;
6
+ /** Comment threads override; defaults to the "main-editor.comment-threads" channel. */
7
+ readonly commentThreads?: readonly MainEditorCommentThread[];
8
+ /** UI locale for the component-fixed strings (default "en"). */
9
+ readonly locale?: MainEditorLocale;
10
+ /** editor — insert + drag controls (default); readonly — published view. */
11
+ readonly variant?: "editor" | "readonly";
12
+ /** Block kinds the insert menu offers (default: all five D10 kinds). */
13
+ readonly enabledBlockKinds?: readonly MainEditorBlockKind[];
14
+ /** Whether insert controls render in the editor variant (default true). */
15
+ readonly showInsertControls?: boolean;
16
+ /** Workspace title line (defaults to the dossier title; titleFormat config key). */
17
+ readonly titleFormat?: (dossier: MainEditorDossier) => string;
18
+ /** Title-bar actions to enable (all default true; titleBarActions config key). */
19
+ readonly titleBarActions?: Readonly<{
20
+ comments?: boolean;
21
+ share?: boolean;
22
+ overflow?: boolean;
23
+ }>;
24
+ /** Editor surface height (default 900). */
25
+ readonly height?: number;
26
+ }
27
+ /**
28
+ * Platform Prism main-editor micro-UI (component id "main-editor") — the
29
+ * single dossier editor axiom component (D9): the title bar (breadcrumb +
30
+ * autosave stamp, DRAFT/V07 state chip, amber-active COMMENTS toggle, SHARE
31
+ * sharing intent, overflow) absorbed from the retired Title Bar component,
32
+ * the typed block stack with hover-revealed +/⠿ insert gutters (D11), and
33
+ * the block-ALIGNED comments rail (D11 — each card sits at its anchor
34
+ * block's vertical position and follows the workspace scroll), all rendered
35
+ * from IDossierDoc-shaped data on the component channels.
36
+ *
37
+ * Channel discipline: the dossier + threads arrive on
38
+ * "main-editor.dossier" / "main-editor.comment-threads"; the COMMENTS toggle
39
+ * drives the in-editor pane through the "main-editor.comments-open" channel
40
+ * (D6 state passing); block focus publishes through "main-editor.focused-
41
+ * block"; the HOSTED embedded blocks own their variant state through their
42
+ * component channels ("geovision-embed.selected-view" /
43
+ * "evidence-table.selected-view") and their ↗ open intents are re-emitted as
44
+ * "main-editor.open-view-requested" events; add/reorder/remove/duplicate are
45
+ * dossier ACTIONS emitted as events for the host to apply (D3 — blocks are
46
+ * added at app runtime through dossier actions, never composition mutation);
47
+ * comment anchors arrive as "main-editor.comment-requested" events the host
48
+ * turns into threads. No Lattice calls are made from the editor's own render
49
+ * path (the hosted geovision block's describeViews/render resolve through the
50
+ * HOST-supplied transport), so no audit events exist for routine reads; the
51
+ * only audit-adjacent emission is the SHARE intent, which the host (not this
52
+ * component) records on IAuditChain.
53
+ *
54
+ * No palette is hardcoded: every color resolves to semantic theme tokens
55
+ * (ok / threat / warn / signal / accent / card / card-dark / input / border /
56
+ * line / text / muted / deep / panel) supplied by the host's MantineProvider.
57
+ */
58
+ export declare function MainEditor({ dossier: dossierProp, commentThreads: threadsProp, locale, variant, enabledBlockKinds, showInsertControls, titleFormat, titleBarActions, height, }: MainEditorProps): import("react").JSX.Element;
59
+ //# sourceMappingURL=MainEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MainEditor.d.ts","sourceRoot":"","sources":["../src/MainEditor.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IACrC,uFAAuF;IACvF,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC7D,gEAAgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IACzC,wEAAwE;IACxE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC5D,2EAA2E;IAC3E,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,MAAM,CAAC;IAC9D,kFAAkF;IAClF,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACjG,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EAAE,WAAW,EACpB,cAAc,EAAE,WAAW,EAC3B,MAAa,EACb,OAAkB,EAClB,iBAAiB,EACjB,kBAAyB,EACzB,WAAW,EACX,eAAe,EACf,MAAY,GACb,EAAE,eAAe,+BA8HjB"}
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ import { Box, Card, Stack, Text } from "@mantine/core";
4
+ import { usePrismStateValue } from "@zephytiju/prism-react";
5
+ import { stringsForLocale } from "./locales/index.js";
6
+ import { TitleBar } from "./TitleBar.js";
7
+ import { BlockStack } from "./blocks/BlockStack.js";
8
+ import { SelectionToolbar } from "./blocks/SelectionToolbar.js";
9
+ import { CommentsPane } from "./CommentsPane.js";
10
+ import { DEEP } from "./tokens.js";
11
+ /**
12
+ * Platform Prism main-editor micro-UI (component id "main-editor") — the
13
+ * single dossier editor axiom component (D9): the title bar (breadcrumb +
14
+ * autosave stamp, DRAFT/V07 state chip, amber-active COMMENTS toggle, SHARE
15
+ * sharing intent, overflow) absorbed from the retired Title Bar component,
16
+ * the typed block stack with hover-revealed +/⠿ insert gutters (D11), and
17
+ * the block-ALIGNED comments rail (D11 — each card sits at its anchor
18
+ * block's vertical position and follows the workspace scroll), all rendered
19
+ * from IDossierDoc-shaped data on the component channels.
20
+ *
21
+ * Channel discipline: the dossier + threads arrive on
22
+ * "main-editor.dossier" / "main-editor.comment-threads"; the COMMENTS toggle
23
+ * drives the in-editor pane through the "main-editor.comments-open" channel
24
+ * (D6 state passing); block focus publishes through "main-editor.focused-
25
+ * block"; the HOSTED embedded blocks own their variant state through their
26
+ * component channels ("geovision-embed.selected-view" /
27
+ * "evidence-table.selected-view") and their ↗ open intents are re-emitted as
28
+ * "main-editor.open-view-requested" events; add/reorder/remove/duplicate are
29
+ * dossier ACTIONS emitted as events for the host to apply (D3 — blocks are
30
+ * added at app runtime through dossier actions, never composition mutation);
31
+ * comment anchors arrive as "main-editor.comment-requested" events the host
32
+ * turns into threads. No Lattice calls are made from the editor's own render
33
+ * path (the hosted geovision block's describeViews/render resolve through the
34
+ * HOST-supplied transport), so no audit events exist for routine reads; the
35
+ * only audit-adjacent emission is the SHARE intent, which the host (not this
36
+ * component) records on IAuditChain.
37
+ *
38
+ * No palette is hardcoded: every color resolves to semantic theme tokens
39
+ * (ok / threat / warn / signal / accent / card / card-dark / input / border /
40
+ * line / text / muted / deep / panel) supplied by the host's MantineProvider.
41
+ */
42
+ export function MainEditor({ dossier: dossierProp, commentThreads: threadsProp, locale = "en", variant = "editor", enabledBlockKinds, showInsertControls = true, titleFormat, titleBarActions, height = 900, }) {
43
+ const strings = stringsForLocale(locale);
44
+ const channelDossier = usePrismStateValue("main-editor.dossier");
45
+ const channelThreads = usePrismStateValue("main-editor.comment-threads");
46
+ // The pane state arrives through the channel (D6): the title-bar toggle
47
+ // publishes it; null defaults to the prototype's open pane.
48
+ const commentsOpenChannel = usePrismStateValue("main-editor.comments-open");
49
+ const commentsOpen = commentsOpenChannel ?? true;
50
+ const focusedBlock = usePrismStateValue("main-editor.focused-block");
51
+ const dossier = dossierProp ?? channelDossier;
52
+ const threads = threadsProp ?? channelThreads;
53
+ // D11 alignment state: the workspace scroll offset the rail mirrors, plus
54
+ // the measured content-space tops of every block row.
55
+ const workspaceRef = useRef(null);
56
+ const [scrollTop, setScrollTop] = useState(0);
57
+ const [blockAnchors, setBlockAnchors] = useState({});
58
+ const measureAnchors = useCallback(() => {
59
+ const workspace = workspaceRef.current;
60
+ if (workspace === null) {
61
+ return;
62
+ }
63
+ const portTop = workspace.getBoundingClientRect().top;
64
+ const next = {};
65
+ workspace.querySelectorAll("[data-block-row-id]").forEach((row) => {
66
+ const id = row.getAttribute("data-block-row-id");
67
+ if (id !== null) {
68
+ next[id] = Math.max(row.getBoundingClientRect().top - portTop + workspace.scrollTop, 0);
69
+ }
70
+ });
71
+ setBlockAnchors(next);
72
+ }, []);
73
+ useLayoutEffect(() => {
74
+ if (dossier === null || dossier === undefined) {
75
+ return;
76
+ }
77
+ // Measure after layout settles, then once more after fonts/async paint.
78
+ const raf = requestAnimationFrame(measureAnchors);
79
+ const later = window.setTimeout(measureAnchors, 300);
80
+ return () => {
81
+ cancelAnimationFrame(raf);
82
+ window.clearTimeout(later);
83
+ };
84
+ }, [dossier, threads, locale, variant, measureAnchors]);
85
+ useEffect(() => {
86
+ const onResize = () => {
87
+ measureAnchors();
88
+ };
89
+ window.addEventListener("resize", onResize);
90
+ return () => {
91
+ window.removeEventListener("resize", onResize);
92
+ };
93
+ }, [measureAnchors]);
94
+ if (dossier === null || dossier === undefined) {
95
+ return (_jsx(Card, { withBorder: true, "data-testid": "main-editor-empty", children: _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { fw: 500, c: "text", children: strings.emptyTitle }), _jsx(Text, { size: "sm", c: "muted", children: strings.emptyHint })] }) }));
96
+ }
97
+ const openCommentCount = (threads ?? []).filter((thread) => thread.status === "open").length;
98
+ const shownDossier = titleFormat === undefined ? dossier : { ...dossier, title: titleFormat(dossier) };
99
+ return (_jsxs(Stack, { gap: 0, miw: 0, style: { background: DEEP, borderRadius: 6, overflow: "hidden", height }, "data-testid": "main-editor", children: [_jsx(TitleBar, { dossier: shownDossier, openCommentCount: openCommentCount, commentsOpen: commentsOpen, locale: locale, actions: titleBarActions }), _jsxs(Box, { style: { position: "relative", flex: 1, overflow: "hidden" }, "data-testid": "main-editor-body", children: [_jsx(Box, { ref: workspaceRef, h: "100%", px: 24, pt: 22, style: { overflowY: "auto", background: "var(--mantine-color-deep-filled)" }, "data-testid": "main-editor-workspace", onScroll: (event) => {
100
+ setScrollTop(event.currentTarget.scrollTop);
101
+ }, children: _jsx(Box, { w: 944, children: _jsx(BlockStack, { dossier: shownDossier, locale: locale, variant: variant, enabledBlockKinds: enabledBlockKinds ?? [], showInsertControls: showInsertControls, focusedBlockId: focusedBlock?.blockId ?? null }) }) }), _jsx(CommentsPane, { threads: threads ?? [], open: commentsOpen, locale: locale, blockOrder: shownDossier.blocks.map((block) => block.id), blockAnchors: blockAnchors, scrollTop: scrollTop }), variant === "editor" ? (_jsx(SelectionToolbar, { locale: locale, containerRef: workspaceRef })) : null] })] }));
102
+ }
@@ -0,0 +1,28 @@
1
+ import type { MainEditorDossier } from "./types.js";
2
+ import type { MainEditorLocale } from "./locales/index.js";
3
+ export interface TitleBarProps {
4
+ readonly dossier: MainEditorDossier;
5
+ /** Open thread count from the comment threads (drives the chip + toggle label). */
6
+ readonly openCommentCount: number;
7
+ /** Whether the in-editor comments pane is open (drives the amber-active state). */
8
+ readonly commentsOpen: boolean;
9
+ readonly locale: MainEditorLocale;
10
+ /** Configuration: title-bar actions to enable (all default true). */
11
+ readonly actions?: Readonly<{
12
+ comments?: boolean;
13
+ share?: boolean;
14
+ overflow?: boolean;
15
+ }>;
16
+ }
17
+ /**
18
+ * The dossier editor title bar, absorbed into the Main Editor axiom component
19
+ * (D9): breadcrumb + autosave stamp on the left; the DRAFT/V07 state chip,
20
+ * the COMMENTS toggle (active state amber) that drives the in-editor pane
21
+ * through the "main-editor.comments-open" channel (D6 state passing, not a
22
+ * direct reference), SHARE (a sharing intent the host audits via IAuditChain
23
+ * — the component never audits routine reads), and the ••• overflow on the
24
+ * right. Publishes through setter-only bindings, so the title bar never
25
+ * re-renders from its own publications.
26
+ */
27
+ export declare function TitleBar({ dossier, openCommentCount, commentsOpen, locale, actions }: TitleBarProps): import("react").JSX.Element;
28
+ //# sourceMappingURL=TitleBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TitleBar.d.ts","sourceRoot":"","sources":["../src/TitleBar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAA0B,MAAM,YAAY,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,mFAAmF;IACnF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,mFAAmF;IACnF,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC1F;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,aAAa,+BAuGnG"}
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Group, Stack, Text, UnstyledButton } from "@mantine/core";
3
+ import { emitPrismEvent, usePrismStateSetter } from "@zephytiju/prism-react";
4
+ import { formatMessage, stringsForLocale } from "./locales/index.js";
5
+ import { ACCENT, BORDER, CARD, CARD_DARK, LINE, MONO, MUTED, TEXT, WARN } from "./tokens.js";
6
+ const btnBase = {
7
+ height: 34,
8
+ background: CARD,
9
+ border: `1px solid ${BORDER}`,
10
+ borderRadius: 5,
11
+ display: "inline-flex",
12
+ alignItems: "center",
13
+ justifyContent: "center",
14
+ fontFamily: MONO,
15
+ fontSize: 9,
16
+ fontWeight: 500,
17
+ color: TEXT,
18
+ flexShrink: 0,
19
+ };
20
+ /**
21
+ * The dossier editor title bar, absorbed into the Main Editor axiom component
22
+ * (D9): breadcrumb + autosave stamp on the left; the DRAFT/V07 state chip,
23
+ * the COMMENTS toggle (active state amber) that drives the in-editor pane
24
+ * through the "main-editor.comments-open" channel (D6 state passing, not a
25
+ * direct reference), SHARE (a sharing intent the host audits via IAuditChain
26
+ * — the component never audits routine reads), and the ••• overflow on the
27
+ * right. Publishes through setter-only bindings, so the title bar never
28
+ * re-renders from its own publications.
29
+ */
30
+ export function TitleBar({ dossier, openCommentCount, commentsOpen, locale, actions }) {
31
+ const strings = stringsForLocale(locale);
32
+ const enabled = { comments: true, share: true, overflow: true, ...actions };
33
+ // Setter-only publication: the toggle never re-renders from its own writes;
34
+ // the open label derives from the commentsOpen prop.
35
+ const publishCommentsOpen = usePrismStateSetter("main-editor.comments-open");
36
+ const commentsLabel = commentsOpen
37
+ ? formatMessage(strings.commentsButtonWithCount, { count: openCommentCount })
38
+ : strings.commentsButton;
39
+ return (_jsxs(Group, { justify: "space-between", align: "center", gap: 12, wrap: "nowrap", px: "14px 18px", h: 58, style: { background: CARD_DARK, borderBottom: `1px solid ${LINE}`, flexShrink: 0 }, "data-testid": "main-editor-title-bar", children: [_jsxs(Stack, { gap: 3, miw: 0, style: { flex: 1, minWidth: 0 }, children: [_jsx(Text, { ff: MONO, fz: 10, fw: 500, style: { color: TEXT, letterSpacing: "0.04em", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, "data-testid": "main-editor-breadcrumb", children: dossier.breadcrumb.join(" / ") }), _jsx(Text, { ff: MONO, fz: 8, style: { color: "var(--mantine-color-ok-filled)", letterSpacing: "0.03em" }, "data-testid": "main-editor-autosave", children: `${dossier.docCode} • ${formatMessage(strings.autosavedAt, { time: dossier.autosavedAt })}` })] }), _jsxs(Group, { gap: 10, wrap: "nowrap", children: [enabled.comments ? (_jsxs(UnstyledButton, { type: "button", style: {
40
+ ...btnBase,
41
+ width: 148,
42
+ gap: 8,
43
+ ...(commentsOpen
44
+ ? { border: `1px solid ${WARN}`, color: WARN }
45
+ : {}),
46
+ }, title: "Expand / collapse comments in the editor body", "data-testid": "main-editor-comments-toggle", "aria-pressed": commentsOpen, onClick: () => {
47
+ publishCommentsOpen(!commentsOpen);
48
+ }, children: [_jsx("span", { "aria-hidden": "true", children: "\u2750" }), commentsLabel] })) : null, _jsxs(Box, { w: 132, h: 34, px: 10, style: { background: CARD, border: `1px solid ${BORDER}`, borderRadius: 5, flexShrink: 0 }, "data-testid": "main-editor-state-chip", children: [_jsx(Text, { ff: MONO, fz: 8, fw: 500, c: "accent", style: { paddingTop: 5, letterSpacing: "0.03em" }, "data-testid": "main-editor-state-version", children: `${dossier.draftState} / ${dossier.version}` }), _jsx(Text, { ff: MONO, fz: 7, style: { color: MUTED, letterSpacing: "0.02em" }, "data-testid": "main-editor-open-comments", children: formatMessage(strings.openCommentsCount, { count: openCommentCount }) })] }), enabled.share ? (_jsxs(UnstyledButton, { type: "button", style: { ...btnBase, width: 100, gap: 8, borderColor: ACCENT, color: ACCENT }, "data-testid": "main-editor-share", onClick: () => {
49
+ // Sharing intent (D8): an action request that never grants
50
+ // authorization; the host performs the share and appends the
51
+ // IAuditChain entry. The component audits nothing itself.
52
+ const request = { docId: dossier.id };
53
+ emitPrismEvent("main-editor.share-requested", request);
54
+ }, children: [_jsx("span", { "aria-hidden": "true", children: "\u2197" }), strings.share] })) : null, enabled.overflow ? (_jsx(UnstyledButton, { type: "button", style: { ...btnBase, width: 46, fontFamily: "inherit", fontSize: 14, fontWeight: 600 }, title: strings.overflowTitle, "data-testid": "main-editor-overflow", children: _jsx("span", { "aria-hidden": "true", children: "\u2022\u2022\u2022" }) })) : null] })] }));
55
+ }