@etiquekit/etq 1.0.13 → 1.0.15

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 (143) hide show
  1. package/AGENTS.md +26 -19
  2. package/LICENSE +3 -2
  3. package/NOTICE +4 -6
  4. package/QuickStart.md +46 -35
  5. package/README.md +57 -55
  6. package/bin/etiquette +5 -1
  7. package/bin/etiquette-core +5 -1
  8. package/docs/ARCHITECTURE.md +17 -20
  9. package/docs/CONCEPTS.md +5 -5
  10. package/docs/CORE_PROFILE.md +15 -7
  11. package/docs/LANE_PROVISIONING.md +2 -7
  12. package/docs/README.md +14 -13
  13. package/docs/RELEASE_SURFACE_AUDIT.md +6 -7
  14. package/docs/SEAT_DISCIPLINE.md +91 -55
  15. package/docs/SEAT_PROVISIONING.md +19 -16
  16. package/docs/TEAM_HANDOFF.md +21 -21
  17. package/docs/WORKTREE_QOL.md +0 -7
  18. package/docs/contracts/ledger-entry/README.md +11 -11
  19. package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +2 -2
  20. package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
  21. package/lib/etiquette-core.js +313 -0
  22. package/lib/etiquette.js +1151 -0
  23. package/package.json +13 -12
  24. package/templates/etiquette-vanilla-v0/README.md +3 -2
  25. package/templates/etiquette-vanilla-v0/source/control-seat/README.md +6 -0
  26. package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
  27. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +5 -0
  28. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
  29. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
  30. package/templates/etiquette-vanilla-v0/validate-vanilla.sh +5 -0
  31. package/templates/hosted-receiver/README.md +41 -0
  32. package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
  33. package/templates/seat-packs-v0/README.md +2 -3
  34. package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -3
  35. package/templates/seat-packs-v0/source/codex-seat/README.md +3 -3
  36. package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -3
  37. package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
  38. package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
  39. package/docs/CONCEPT_STATUS.md +0 -66
  40. package/packages/control/src/authority/lease.ts +0 -261
  41. package/packages/control/src/authority/node-delegation.ts +0 -257
  42. package/packages/control/src/authority/rig-conductor.ts +0 -632
  43. package/packages/control/src/cli/argv.ts +0 -155
  44. package/packages/control/src/cli/commands/console.ts +0 -200
  45. package/packages/control/src/cli/commands/dispatch-core.ts +0 -89
  46. package/packages/control/src/cli/commands/dispatch.ts +0 -279
  47. package/packages/control/src/cli/commands/harness.ts +0 -89
  48. package/packages/control/src/cli/commands/hook.ts +0 -50
  49. package/packages/control/src/cli/commands/ledger.ts +0 -91
  50. package/packages/control/src/cli/commands/local-workflow.ts +0 -4686
  51. package/packages/control/src/cli/commands/memory.ts +0 -445
  52. package/packages/control/src/cli/commands/release.ts +0 -108
  53. package/packages/control/src/cli/commands/rubric.ts +0 -103
  54. package/packages/control/src/cli/commands/seat.ts +0 -179
  55. package/packages/control/src/cli/commands/session.ts +0 -127
  56. package/packages/control/src/cli/commands/supervision.ts +0 -246
  57. package/packages/control/src/cli/commands/sync.ts +0 -119
  58. package/packages/control/src/cli/commands/workflow.ts +0 -86
  59. package/packages/control/src/cli/commands/workspace.ts +0 -50
  60. package/packages/control/src/cli/core-usage.ts +0 -34
  61. package/packages/control/src/cli/prompt.ts +0 -67
  62. package/packages/control/src/cli/supervision-deps.ts +0 -44
  63. package/packages/control/src/cli/usage.ts +0 -241
  64. package/packages/control/src/cli.ts +0 -207
  65. package/packages/control/src/core-cli.ts +0 -50
  66. package/packages/control/src/dispatch/decision.ts +0 -202
  67. package/packages/control/src/dispatch/projection.ts +0 -293
  68. package/packages/control/src/dispatch/record.ts +0 -153
  69. package/packages/control/src/engagement/project.ts +0 -170
  70. package/packages/control/src/fs.ts +0 -19
  71. package/packages/control/src/harness/pruning.ts +0 -406
  72. package/packages/control/src/hooks/dispatcher.ts +0 -117
  73. package/packages/control/src/hooks/outbox.ts +0 -86
  74. package/packages/control/src/hooks/sanitize.ts +0 -6
  75. package/packages/control/src/hooks/types.ts +0 -34
  76. package/packages/control/src/index.ts +0 -384
  77. package/packages/control/src/ledger/entry.ts +0 -303
  78. package/packages/control/src/ledger/indexer.ts +0 -542
  79. package/packages/control/src/memory/context.ts +0 -149
  80. package/packages/control/src/memory/drain-import.ts +0 -207
  81. package/packages/control/src/memory/indexer.ts +0 -284
  82. package/packages/control/src/memory/query.ts +0 -75
  83. package/packages/control/src/memory/sanitize.ts +0 -50
  84. package/packages/control/src/memory/sharded-drain-import.ts +0 -212
  85. package/packages/control/src/memory/status.ts +0 -211
  86. package/packages/control/src/memory/store-lifecycle.ts +0 -509
  87. package/packages/control/src/memory/store.ts +0 -284
  88. package/packages/control/src/memory/types.ts +0 -146
  89. package/packages/control/src/parity/surfaces.ts +0 -748
  90. package/packages/control/src/project.ts +0 -141
  91. package/packages/control/src/projection/local-ledger-view.ts +0 -373
  92. package/packages/control/src/projection/return-enforcement.ts +0 -48
  93. package/packages/control/src/projection/timeline-preview.ts +0 -539
  94. package/packages/control/src/projection/timeline.ts +0 -708
  95. package/packages/control/src/release/readiness.ts +0 -831
  96. package/packages/control/src/rubric/loader.ts +0 -326
  97. package/packages/control/src/rubric/promotion.ts +0 -54
  98. package/packages/control/src/rubric/runner.ts +0 -159
  99. package/packages/control/src/rubric/types.ts +0 -158
  100. package/packages/control/src/seat/owner-card.ts +0 -388
  101. package/packages/control/src/seat/readiness.ts +0 -834
  102. package/packages/control/src/session/runbook.ts +0 -431
  103. package/packages/control/src/shared/sanitize.ts +0 -49
  104. package/packages/control/src/supervision/action-classes.ts +0 -192
  105. package/packages/control/src/supervision/command-apply.ts +0 -378
  106. package/packages/control/src/supervision/errors.ts +0 -14
  107. package/packages/control/src/supervision/event-replay.ts +0 -155
  108. package/packages/control/src/supervision/events.ts +0 -109
  109. package/packages/control/src/supervision/index.ts +0 -16
  110. package/packages/control/src/supervision/manifest.ts +0 -127
  111. package/packages/control/src/supervision/paths.ts +0 -49
  112. package/packages/control/src/supervision/projection-adapter.ts +0 -274
  113. package/packages/control/src/supervision/projection.ts +0 -75
  114. package/packages/control/src/supervision/rebuild.ts +0 -99
  115. package/packages/control/src/supervision/session-open.ts +0 -131
  116. package/packages/control/src/supervision/session-read.ts +0 -99
  117. package/packages/control/src/supervision/sqlite-impl.ts +0 -71
  118. package/packages/control/src/supervision/sqlite.ts +0 -121
  119. package/packages/control/src/supervision/store-rows.ts +0 -371
  120. package/packages/control/src/supervision/turn-close.ts +0 -154
  121. package/packages/control/src/supervision/turn-open.ts +0 -284
  122. package/packages/control/src/sync/event-log-merge-driver.ts +0 -263
  123. package/packages/control/src/sync/join-plan.ts +0 -375
  124. package/packages/control/src/sync/outbox.ts +0 -492
  125. package/packages/control/src/workflow/evaluator.ts +0 -140
  126. package/packages/control/src/workflow/loader.ts +0 -200
  127. package/packages/control/src/workflow/types.ts +0 -90
  128. package/packages/control/src/workspace/authority.ts +0 -499
  129. package/packages/protocol/src/guards.ts +0 -119
  130. package/packages/protocol/src/huddle-board.ts +0 -198
  131. package/packages/protocol/src/huddle.ts +0 -295
  132. package/packages/protocol/src/incident.ts +0 -251
  133. package/packages/protocol/src/index.ts +0 -8
  134. package/packages/protocol/src/interfaces.ts +0 -107
  135. package/packages/protocol/src/packet-profile.ts +0 -195
  136. package/packages/protocol/src/state.ts +0 -81
  137. package/packages/protocol/src/types.ts +0 -434
  138. package/release/lineage.v0.json +0 -14
  139. package/scripts/release-candidate-verify.sh +0 -175
  140. package/scripts/release-checksum.sh +0 -25
  141. package/scripts/release-pack-canary.sh +0 -97
  142. package/scripts/release-scan.sh +0 -249
  143. package/scripts/release-sign.sh +0 -34
@@ -1,539 +0,0 @@
1
- import { applyTimelineDeepLink, type TimelineFeedItem, type TimelineItemType, type TimelineView } from './timeline';
2
-
3
- export const TIMELINE_PREVIEW_ALLOWED_METHODS = ['GET', 'HEAD', 'OPTIONS'] as const;
4
-
5
- function escapeHtml(value: string): string {
6
- return value
7
- .replace(/&/g, '&')
8
- .replace(/</g, '&lt;')
9
- .replace(/>/g, '&gt;')
10
- .replace(/"/g, '&quot;')
11
- .replace(/'/g, '&#39;');
12
- }
13
-
14
- function titleCase(value: string): string {
15
- return value.charAt(0).toUpperCase() + value.slice(1);
16
- }
17
-
18
- function itemBadge(itemType: TimelineItemType): string {
19
- switch (itemType) {
20
- case 'task_card':
21
- return 'TASK';
22
- case 'return':
23
- return 'RETURN';
24
- case 'receipt':
25
- return 'RECEIPT';
26
- case 'decision':
27
- return 'DECISION';
28
- case 'chatter':
29
- return 'CHATTER';
30
- }
31
- }
32
-
33
- function itemTone(itemType: TimelineItemType): string {
34
- switch (itemType) {
35
- case 'task_card':
36
- return 'task';
37
- case 'return':
38
- return 'return';
39
- case 'receipt':
40
- return 'receipt';
41
- case 'decision':
42
- return 'decision';
43
- case 'chatter':
44
- return 'chatter';
45
- }
46
- }
47
-
48
- function sourceRef(item: TimelineFeedItem): string {
49
- return item.source.event_ref ?? item.source.packet_ref ?? 'unsequenced';
50
- }
51
-
52
- function participants(item: TimelineFeedItem): string {
53
- const parts = [
54
- item.actor_seat_id ? `from ${item.actor_seat_id}` : null,
55
- item.seat_id ? `seat ${item.seat_id}` : null,
56
- item.operator_principal ? `operator ${item.operator_principal}` : null,
57
- ].filter((part): part is string => Boolean(part));
58
- return parts.length > 0 ? parts.join(' / ') : 'seat unresolved';
59
- }
60
-
61
- function chip(label: string, value: string | null, className = ''): string {
62
- return value ? `<span class="chip ${escapeHtml(className)}"><strong>${escapeHtml(label)}</strong>${escapeHtml(value)}</span>` : '';
63
- }
64
-
65
- function renderSections(item: TimelineFeedItem): string {
66
- return `<div class="sections" role="tablist" aria-label="Card sections">
67
- ${item.display.sections.map((section, index) => `<section class="card-section" role="tabpanel" aria-labelledby="${escapeHtml(item.item_id)}-tab-${index}">
68
- <button id="${escapeHtml(item.item_id)}-tab-${index}" type="button" class="section-tab">${escapeHtml(section.label)}</button>
69
- <p>${escapeHtml(section.body)}</p>
70
- </section>`).join('\n')}
71
- </div>`;
72
- }
73
-
74
- function renderItem(item: TimelineFeedItem): string {
75
- const tone = itemTone(item.item_type);
76
- const seqAnchor = item.source.seq === null ? escapeHtml(item.item_id) : `seq-${String(item.source.seq)}`;
77
- return `<article id="${seqAnchor}" class="feed-item ${tone}" data-item-type="${escapeHtml(item.item_type)}" data-thread-id="${escapeHtml(item.thread.thread_id)}" data-depth="${String(item.thread.depth)}">
78
- <div class="item-head">
79
- <span class="badge ${tone}">${escapeHtml(itemBadge(item.item_type))}</span>
80
- <time>${escapeHtml(item.created_at ?? 'time unknown')}</time>
81
- </div>
82
- <div class="chips" aria-label="Timeline metadata">
83
- ${chip('seat', item.display.seat_badge, 'seat')}
84
- ${chip('role', item.display.role_chip, 'role')}
85
- ${chip('lane', item.display.lane_chip, 'lane')}
86
- ${chip('verdict', item.display.verdict_pill, 'verdict-pill')}
87
- ${chip('status', item.display.status_subtitle, 'status')}
88
- ${item.display.topic_chips.map((topic) => chip('topic', topic, 'topic')).join('')}
89
- ${item.requires_ack === true ? '<span class="chip ack"><strong>ack</strong>requires_ack</span>' : ''}
90
- </div>
91
- <h2>${escapeHtml(item.title)}</h2>
92
- <p class="body">${escapeHtml(item.body)}</p>
93
- ${renderSections(item)}
94
- <dl>
95
- <div><dt>Source</dt><dd><code>${escapeHtml(sourceRef(item))}</code></dd></div>
96
- <div><dt>Participants</dt><dd>${escapeHtml(participants(item))}</dd></div>
97
- ${item.lane ? `<div><dt>Lane</dt><dd><code>${escapeHtml(item.lane)}</code></dd></div>` : ''}
98
- <div><dt>Thread</dt><dd><code>${escapeHtml(item.thread.thread_id)}</code> - ${String(item.thread.thread_item_count)} item${item.thread.thread_item_count === 1 ? '' : 's'}</dd></div>
99
- <div><dt>Authority</dt><dd>projection only; cannot execute, write back, grant, close, merge, or sign</dd></div>
100
- </dl>
101
- </article>`;
102
- }
103
-
104
- function renderThreadIndex(view: TimelineView): string {
105
- if (view.feed.threads.length === 0) return '<section class="empty">No threads found for this scope.</section>';
106
- return `<ol class="thread-index">
107
- ${view.feed.threads.map((thread) => `<li>
108
- <a href="#${escapeHtml(thread.latest_seq === null ? thread.item_ids[0] : `seq-${thread.latest_seq}`)}">${escapeHtml(thread.title)}</a>
109
- <span>${String(thread.item_count)} items</span>
110
- </li>`).join('\n')}
111
- </ol>`;
112
- }
113
-
114
- function renderCuratorPortal(view: TimelineView): string {
115
- const seats = view.curator_portal.health_board.seats.length > 0
116
- ? view.curator_portal.health_board.seats.map((seat) => `<tr>
117
- <td><code>${escapeHtml(seat.seat_id)}</code></td>
118
- <td>${escapeHtml(seat.lifecycle_state)}</td>
119
- <td>${seat.ready ? 'ready' : 'not ready'}</td>
120
- <td>${escapeHtml(seat.source)}</td>
121
- <td><code>${escapeHtml(seat.sentinel_state_ref)}</code></td>
122
- </tr>`).join('\n')
123
- : '<tr><td colspan="5">No seats in this projection.</td></tr>';
124
- const queue = view.curator_portal.promotion_queue.items.length > 0
125
- ? view.curator_portal.promotion_queue.items.map((item) => `<li><code>${escapeHtml(item.source_ref)}</code> ${escapeHtml(item.reason)} ${item.next_owner ? `- next ${escapeHtml(item.next_owner)}` : ''}</li>`).join('\n')
126
- : '<li>No display-only queue items.</li>';
127
- return `<section class="curator" aria-label="Curator Portal">
128
- <div class="section-title">
129
- <h2>Curator Portal</h2>
130
- <span>display-only</span>
131
- </div>
132
- <div class="curator-grid">
133
- <section>
134
- <h3>Health board</h3>
135
- <table>
136
- <thead><tr><th>Seat</th><th>Lifecycle</th><th>Ready</th><th>Source</th><th>Sentinel</th></tr></thead>
137
- <tbody>${seats}</tbody>
138
- </table>
139
- </section>
140
- <section>
141
- <h3>Digest view</h3>
142
- <dl>
143
- <div><dt>Threads</dt><dd>${String(view.curator_portal.digest_view.threads)}</dd></div>
144
- <div><dt>Requires ack</dt><dd>${String(view.curator_portal.digest_view.requires_ack)}</dd></div>
145
- <div><dt>Tasks</dt><dd>${String(view.curator_portal.digest_view.items_by_type.task_card)}</dd></div>
146
- <div><dt>Returns</dt><dd>${String(view.curator_portal.digest_view.items_by_type.return)}</dd></div>
147
- </dl>
148
- </section>
149
- <section>
150
- <h3>Promotion queue</h3>
151
- <p>Display-only; cannot promote, grant, close, merge, execute, or write back.</p>
152
- <ul>${queue}</ul>
153
- </section>
154
- </div>
155
- </section>`;
156
- }
157
-
158
- export function renderTimelinePreviewHtml(view: TimelineView): string {
159
- const items = view.feed.items.length > 0
160
- ? view.feed.items.map((item) => renderItem(item)).join('\n')
161
- : '<section class="empty">No timeline items found for this scope.</section>';
162
- const selected = view.feed.deep_link.item_id ? `Selected ${view.feed.deep_link.event_ref ?? view.feed.deep_link.item_id}` : 'No deep link selected';
163
- const scopeLabel = titleCase(view.scope);
164
- return `<!doctype html>
165
- <html lang="en">
166
- <head>
167
- <meta charset="utf-8">
168
- <meta name="viewport" content="width=device-width, initial-scale=1">
169
- <title>Etiquette Timeline Preview</title>
170
- <style>
171
- :root {
172
- color-scheme: light;
173
- --ink: #172026;
174
- --muted: #5d6973;
175
- --line: #d7dee5;
176
- --surface: #ffffff;
177
- --page: #f6f7f8;
178
- --teal: #0f766e;
179
- --amber: #a15c00;
180
- --red: #b42318;
181
- --blue: #2563eb;
182
- --violet: #6d28d9;
183
- }
184
- * { box-sizing: border-box; }
185
- body {
186
- margin: 0;
187
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
188
- color: var(--ink);
189
- background: var(--page);
190
- }
191
- main { width: min(1120px, calc(100% - 32px)); margin: 0 auto; padding: 24px 0 40px; }
192
- header {
193
- display: grid;
194
- grid-template-columns: minmax(0, 1fr) auto;
195
- gap: 16px;
196
- align-items: end;
197
- padding: 18px 0 16px;
198
- border-bottom: 1px solid var(--line);
199
- }
200
- h1 { margin: 0; font-size: 28px; line-height: 1.15; letter-spacing: 0; }
201
- .subhead { margin: 6px 0 0; color: var(--muted); line-height: 1.45; }
202
- .scope {
203
- display: inline-flex;
204
- align-items: center;
205
- min-height: 32px;
206
- padding: 6px 10px;
207
- border-radius: 8px;
208
- background: #e6f4f1;
209
- color: #075e57;
210
- font-weight: 700;
211
- white-space: nowrap;
212
- }
213
- .summary {
214
- display: grid;
215
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
216
- gap: 10px;
217
- margin: 18px 0;
218
- }
219
- .metric {
220
- background: var(--surface);
221
- border: 1px solid var(--line);
222
- border-radius: 8px;
223
- padding: 12px;
224
- min-width: 0;
225
- }
226
- .metric span { display: block; color: var(--muted); font-size: 12px; }
227
- .metric strong { display: block; margin-top: 4px; font-size: 18px; overflow-wrap: anywhere; }
228
- .boundary {
229
- display: grid;
230
- gap: 8px;
231
- margin: 18px 0;
232
- padding: 14px;
233
- border: 1px solid #f0c36d;
234
- border-left: 4px solid var(--amber);
235
- border-radius: 8px;
236
- background: #fff8eb;
237
- }
238
- .boundary strong { color: #7a4100; }
239
- .view-controls {
240
- display: flex;
241
- flex-wrap: wrap;
242
- align-items: center;
243
- justify-content: space-between;
244
- gap: 10px;
245
- margin: 18px 0 10px;
246
- }
247
- .toggle {
248
- display: inline-flex;
249
- overflow: hidden;
250
- border: 1px solid var(--line);
251
- border-radius: 8px;
252
- background: var(--surface);
253
- }
254
- .toggle a {
255
- min-height: 34px;
256
- padding: 8px 12px;
257
- border-right: 1px solid var(--line);
258
- color: var(--ink);
259
- text-decoration: none;
260
- font-weight: 700;
261
- }
262
- .toggle a:last-child { border-right: 0; }
263
- .toggle a[aria-current="true"] { background: #e6f4f1; color: #075e57; }
264
- .thread-index {
265
- display: grid;
266
- gap: 8px;
267
- margin: 0 0 18px;
268
- padding: 0;
269
- list-style: none;
270
- }
271
- .thread-index li {
272
- display: flex;
273
- gap: 10px;
274
- justify-content: space-between;
275
- padding: 10px 12px;
276
- border: 1px solid var(--line);
277
- border-radius: 8px;
278
- background: var(--surface);
279
- }
280
- .thread-index a { color: var(--blue); font-weight: 700; overflow-wrap: anywhere; }
281
- .feed { display: grid; gap: 12px; margin-top: 18px; }
282
- .feed-item {
283
- background: var(--surface);
284
- border: 1px solid var(--line);
285
- border-left: 4px solid var(--teal);
286
- border-radius: 8px;
287
- padding: 14px;
288
- }
289
- .feed-item.return { border-left-color: var(--blue); }
290
- .feed-item.receipt { border-left-color: var(--violet); }
291
- .feed-item.decision { border-left-color: var(--red); }
292
- .feed-item.chatter { border-left-color: #64748b; }
293
- .item-head { display: flex; gap: 10px; align-items: center; justify-content: space-between; }
294
- .badge {
295
- display: inline-flex;
296
- min-height: 24px;
297
- align-items: center;
298
- padding: 3px 8px;
299
- border-radius: 6px;
300
- background: #e6f4f1;
301
- color: #075e57;
302
- font-size: 12px;
303
- font-weight: 800;
304
- }
305
- .badge.return { background: #eaf1ff; color: #1d4ed8; }
306
- .badge.receipt { background: #f1eafe; color: #5b21b6; }
307
- .badge.decision { background: #feeceb; color: #991b1b; }
308
- .badge.chatter { background: #eef2f5; color: #475569; }
309
- .chips {
310
- display: flex;
311
- flex-wrap: wrap;
312
- gap: 6px;
313
- margin-top: 10px;
314
- }
315
- .chip {
316
- display: inline-flex;
317
- gap: 6px;
318
- align-items: center;
319
- min-height: 24px;
320
- padding: 3px 8px;
321
- border: 1px solid #d5dde6;
322
- border-radius: 999px;
323
- background: #f8fafc;
324
- color: #334155;
325
- font-size: 12px;
326
- }
327
- .chip strong { color: #64748b; font-weight: 800; }
328
- .chip.verdict-pill { border-color: #bfd1ff; background: #eaf1ff; color: #1d4ed8; }
329
- .chip.ack { border-color: #f5c2c0; background: #feeceb; color: #991b1b; }
330
- time { color: var(--muted); font-size: 12px; text-align: right; }
331
- h2 { margin: 10px 0 6px; font-size: 17px; letter-spacing: 0; line-height: 1.25; }
332
- .body { margin: 0 0 12px; color: #334155; overflow-wrap: anywhere; }
333
- .sections {
334
- display: grid;
335
- grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
336
- gap: 8px;
337
- margin: 12px 0;
338
- }
339
- .card-section {
340
- padding: 8px;
341
- border: 1px solid var(--line);
342
- border-radius: 8px;
343
- background: #fbfcfd;
344
- }
345
- .section-tab {
346
- border: 0;
347
- padding: 0;
348
- background: transparent;
349
- color: var(--ink);
350
- font-weight: 800;
351
- text-align: left;
352
- }
353
- .card-section p { margin: 6px 0 0; color: var(--muted); font-size: 13px; line-height: 1.35; }
354
- dl {
355
- display: grid;
356
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
357
- gap: 8px 14px;
358
- margin: 0;
359
- }
360
- dl div { min-width: 0; }
361
- dt { color: var(--muted); font-size: 12px; }
362
- dd { margin: 2px 0 0; overflow-wrap: anywhere; }
363
- code {
364
- padding: 2px 5px;
365
- border-radius: 4px;
366
- background: #eef2f5;
367
- color: #263238;
368
- font-size: 12px;
369
- }
370
- .empty {
371
- padding: 20px;
372
- border: 1px solid var(--line);
373
- border-radius: 8px;
374
- background: var(--surface);
375
- color: var(--muted);
376
- }
377
- .curator {
378
- margin-top: 22px;
379
- padding-top: 18px;
380
- border-top: 1px solid var(--line);
381
- }
382
- .section-title {
383
- display: flex;
384
- align-items: center;
385
- justify-content: space-between;
386
- gap: 12px;
387
- margin-bottom: 10px;
388
- }
389
- .section-title h2 { margin: 0; }
390
- .section-title span {
391
- padding: 4px 8px;
392
- border-radius: 999px;
393
- background: #eef2f5;
394
- color: #475569;
395
- font-size: 12px;
396
- font-weight: 800;
397
- }
398
- .curator-grid {
399
- display: grid;
400
- grid-template-columns: minmax(0, 1.4fr) minmax(220px, .8fr);
401
- gap: 12px;
402
- }
403
- .curator-grid section {
404
- min-width: 0;
405
- padding: 12px;
406
- border: 1px solid var(--line);
407
- border-radius: 8px;
408
- background: var(--surface);
409
- }
410
- .curator-grid section:first-child { grid-row: span 2; }
411
- .curator h3 { margin: 0 0 8px; font-size: 16px; }
412
- table {
413
- width: 100%;
414
- border-collapse: collapse;
415
- font-size: 13px;
416
- }
417
- th, td {
418
- padding: 7px 6px;
419
- border-bottom: 1px solid var(--line);
420
- text-align: left;
421
- vertical-align: top;
422
- overflow-wrap: anywhere;
423
- }
424
- th { color: var(--muted); font-size: 12px; }
425
- ul { margin: 8px 0 0; padding-left: 18px; }
426
- @media (max-width: 640px) {
427
- main { width: min(100% - 20px, 1120px); padding-top: 12px; }
428
- header { grid-template-columns: 1fr; align-items: start; }
429
- .scope { justify-self: start; }
430
- .item-head { align-items: flex-start; flex-direction: column; }
431
- time { text-align: left; }
432
- .curator-grid { grid-template-columns: 1fr; }
433
- }
434
- </style>
435
- </head>
436
- <body data-authority="projection-only" data-ledger-write-path="false">
437
- <main>
438
- <header>
439
- <div>
440
- <h1>Etiquette Timeline Preview</h1>
441
- <p class="subhead">Read-only loopback panel over the existing timeline projection.</p>
442
- </div>
443
- <span class="scope">${escapeHtml(scopeLabel)}</span>
444
- </header>
445
- <section class="summary" aria-label="Projection summary">
446
- <div class="metric"><span>Schema</span><strong>${escapeHtml(view.schema)}</strong></div>
447
- <div class="metric"><span>Items</span><strong>${String(view.feed.items_matched)}</strong></div>
448
- <div class="metric"><span>Source</span><strong>${escapeHtml(view.source.projection_schema)}</strong></div>
449
- <div class="metric"><span>Watermark</span><strong>${escapeHtml(view.watermark.latest_event_ref)}</strong></div>
450
- <div class="metric"><span>Commit</span><strong>${escapeHtml(view.watermark.commit_sha)}</strong></div>
451
- <div class="metric"><span>Event log hash</span><strong>${escapeHtml(view.watermark.event_log_hash)}</strong></div>
452
- </section>
453
- <section class="boundary">
454
- <strong>Boundary</strong>
455
- <span>${escapeHtml(view.boundary)}</span>
456
- <span>GET/HEAD/OPTIONS only; no hosted backend, no auth provider, no impersonation, no execution, no write-back.</span>
457
- </section>
458
- <section class="view-controls" aria-label="Feed modes">
459
- <nav class="toggle" aria-label="Timeline display modes">
460
- <a href="#stream" aria-current="true">Stream</a>
461
- <a href="#threads">Thread</a>
462
- </nav>
463
- <span>${escapeHtml(selected)}</span>
464
- </section>
465
- <section id="threads" aria-label="Thread index">
466
- ${renderThreadIndex(view)}
467
- </section>
468
- <section id="stream" class="feed" aria-label="Timeline feed">
469
- ${items}
470
- </section>
471
- ${renderCuratorPortal(view)}
472
- </main>
473
- </body>
474
- </html>`;
475
- }
476
-
477
- function previewHeaders(contentType: string): Headers {
478
- return new Headers({
479
- 'allow': TIMELINE_PREVIEW_ALLOWED_METHODS.join(', '),
480
- 'cache-control': 'no-store',
481
- 'content-type': contentType,
482
- 'x-etiquette-authority': 'projection-only',
483
- 'x-etiquette-ledger-write-path': 'false',
484
- });
485
- }
486
-
487
- function emptyIfHead(request: Request, body: string): string | null {
488
- return request.method === 'HEAD' ? null : body;
489
- }
490
-
491
- export async function timelinePreviewResponseForRequest(
492
- request: Request,
493
- buildView: () => Promise<TimelineView>,
494
- ): Promise<Response> {
495
- if (request.method === 'OPTIONS') {
496
- return new Response(null, { status: 204, headers: previewHeaders('text/plain; charset=utf-8') });
497
- }
498
- if (request.method !== 'GET' && request.method !== 'HEAD') {
499
- return Response.json(
500
- {
501
- error: 'method_not_allowed',
502
- allowed_methods: TIMELINE_PREVIEW_ALLOWED_METHODS,
503
- authority_boundary: {
504
- request_can_write_ledger: false,
505
- request_can_execute: false,
506
- request_can_grant: false,
507
- },
508
- },
509
- { status: 405, headers: previewHeaders('application/json; charset=utf-8') },
510
- );
511
- }
512
-
513
- const url = new URL(request.url);
514
- const requestedSeq = Number.parseInt(url.searchParams.get('seq') ?? '', 10);
515
- const selectSeq = Number.isFinite(requestedSeq) ? requestedSeq : null;
516
- if (url.pathname === '/api/timeline') {
517
- const view = applyTimelineDeepLink(await buildView(), selectSeq);
518
- return new Response(emptyIfHead(request, JSON.stringify(view, null, 2)), {
519
- headers: previewHeaders('application/json; charset=utf-8'),
520
- });
521
- }
522
- if (url.pathname === '/' || url.pathname === '/index.html') {
523
- const view = applyTimelineDeepLink(await buildView(), selectSeq);
524
- return new Response(emptyIfHead(request, renderTimelinePreviewHtml(view)), {
525
- headers: previewHeaders('text/html; charset=utf-8'),
526
- });
527
- }
528
- return Response.json(
529
- {
530
- error: 'not_found',
531
- authority_boundary: {
532
- request_can_write_ledger: false,
533
- request_can_execute: false,
534
- request_can_grant: false,
535
- },
536
- },
537
- { status: 404, headers: previewHeaders('application/json; charset=utf-8') },
538
- );
539
- }