claude-memory-admin 1.4.0 → 1.5.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 CHANGED
@@ -80,7 +80,9 @@ project is to the cliff, and makes it quick to get back under it.
80
80
  session transcripts on a retention period but never touches `memory/`, so a
81
81
  project eventually loses the only proof of what it was called. **Remember
82
82
  path** records one you confirm, and is the single thing this app writes
83
- outside a `memory/` directory.
83
+ outside a `memory/` directory. It is now offered *before* that happens: the
84
+ project header counts down the days until the last transcript naming the
85
+ project is swept, rather than waiting until the name is already gone.
84
86
  - **Where the store is** is read the way Claude Code reads it: `autoMemoryDirectory`
85
87
  from any settings layer, managed policy through project and local, not just
86
88
  `~/.claude/settings.json`. A value that is neither absolute nor `~/`-prefixed
@@ -96,13 +98,28 @@ project is to the cliff, and makes it quick to get back under it.
96
98
  - **Prune** (see below).
97
99
  - **Graph** the wikilinks between memories. Hovering dims everything that is not
98
100
  a neighbour, which is the only practical way to read a dense cluster.
101
+ - **Where each memory came from.** Claude Code stamps `originSessionId` into a
102
+ memory's frontmatter, and the transcript it names sits next to the store until
103
+ the sweep takes it. The memory reads *written in "Release process notes", on
104
+ `main`* while that transcript is there, and says so plainly once it is gone:
105
+ a swept id is struck through in red, like a dead wikilink, because why the
106
+ memory exists can no longer be traced from anything on disk.
107
+ - **Sessions**: the transcripts beside a store, with the retention window drawn
108
+ the way MEMORY.md's cutoff is - each session a tick, the sweep line where it
109
+ falls. Titles are the ones Claude Code generated, falling back to the session
110
+ slug and then the opening prompt; a session that names itself nowhere in the
111
+ part read is shown by id rather than given an invented name. Only the head of
112
+ each file is ever read, so a 200MB store of transcripts costs a quarter of a
113
+ second, and nothing on this tab deletes one.
99
114
  - **Health**: orphans, dangling pointers, broken wikilinks, files linked only
100
115
  mid-sentence, `name` fields that disagree with the filename, two files claiming
101
116
  one `name`, a file bulleted twice, a blank `description`, a `type` outside the
102
117
  four documented ones, a memory that is frontmatter and little else, a hook that
103
118
  only restates the description it points at, a heading with nothing under it, and
104
- entries that spill onto a second line. An orphan can be given the `MEMORY.md`
105
- bullet it is missing without leaving the page.
119
+ entries that spill onto a second line, a memory whose origin transcript has
120
+ been swept, a project whose last proof of its own path is about to be, and
121
+ sessions that produced no memory at all while auto memory was on. An orphan
122
+ can be given the `MEMORY.md` bullet it is missing without leaving the page.
106
123
  - **Every count is visible before you click.** Each tab carries a badge, coloured
107
124
  amber for something to tidy and red for something actively broken, and the
108
125
  sidebar gives each store a dot of its worst severity - memory, instructions and
@@ -312,6 +329,11 @@ claude-memory-admin --root /tmp/memory-snapshot
312
329
  chain, unless you switch on the path check above, which then also walks that one
313
330
  project's directory. It only ever reads: no path a memory names is opened, only
314
331
  looked up in an index built from the project itself.
332
+ - Session transcripts are read at the head only, 16KB and then at most 128KB, and
333
+ never deleted or rewritten. A transcript here reaches 13MB and the app has no
334
+ reason to hold one in memory. A session id read out of frontmatter is matched
335
+ against a strict id shape before it is joined to a path, and only ever looked
336
+ for in the store's own directory.
315
337
  - Every write target must resolve to a plain `.md` file inside that project's own
316
338
  `memory/` directory. `..`, absolute paths and subdirectories are refused.
317
339
  - One exception, and only when you ask for it: **Remember path** writes
@@ -355,6 +377,7 @@ ready to serve and never builds anything. After editing the source, run
355
377
  | `src/projects.mjs` | Project discovery, slug → real path resolution |
356
378
  | `src/settings.mjs` | Layered reads of Claude Code's settings files, and the report behind the Settings tab |
357
379
  | `src/pathcache.mjs` | The opt-in record of confirmed project paths |
380
+ | `src/sessions.mjs` | Session transcripts: bounded head reads, retention, provenance |
358
381
  | `src/stores.mjs` | Store discovery: the user scope, auto memory and the three agent scopes |
359
382
  | `src/instructions.mjs` | CLAUDE.md chain, `@` imports and rules resolution |
360
383
  | `src/parse.mjs` | `MEMORY.md` and frontmatter parsers, wikilinks |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-admin",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Browse, audit and prune the auto memory Claude Code keeps under ~/.claude/projects",
5
5
  "keywords": [
6
6
  "claude",
package/public/app.mjs CHANGED
@@ -17,7 +17,7 @@ const state = {
17
17
  pruneSort: 'oldest',
18
18
  pruneSelection: new Set(),
19
19
  indexView: localStorage.getItem('memoryIndexView') === 'source' ? 'source' : 'rendered',
20
- aux: { instructions: null, settings: null },
20
+ aux: { instructions: null, settings: null, sessions: null },
21
21
  pathCheck: null,
22
22
  };
23
23
 
@@ -153,6 +153,7 @@ const TABS = [
153
153
  { id: 'graph', label: 'Graph' },
154
154
  { id: 'prune', label: 'Prune' },
155
155
  { id: 'context', label: 'Context' },
156
+ { id: 'sessions', label: 'Sessions' },
156
157
  { id: 'health', label: 'Health' },
157
158
  { id: 'settings', label: 'Settings' },
158
159
  { id: 'trash', label: 'Trash' },
@@ -169,6 +170,7 @@ function renderTabs() {
169
170
  for (const tab of TABS) {
170
171
  if (global && tab.id !== 'context') continue;
171
172
  if (!global && tab.id === 'context' && !hasProjectDir) continue;
173
+ if (tab.id === 'sessions' && !state.store.sessions?.count) continue;
172
174
  let badge = null;
173
175
  let tone = 'neutral';
174
176
  if (tab.id === 'memories') badge = String(state.store.memories.length);
@@ -181,6 +183,10 @@ function renderTabs() {
181
183
  badge = '!';
182
184
  tone = state.store.stats.index.level === 'over' ? 'bad' : 'warn';
183
185
  }
186
+ if (tab.id === 'sessions' && state.store.sessions) {
187
+ badge = String(state.store.sessions.count);
188
+ if (state.store.sessions.expiringCount) tone = 'warn';
189
+ }
184
190
  if (tab.id === 'context' && state.aux.instructions?.problems.length) {
185
191
  badge = String(state.aux.instructions.problems.length);
186
192
  tone = worstSeverity(state.aux.instructions.problems);
@@ -278,13 +284,16 @@ function renderDetail() {
278
284
  ];
279
285
  if (!memory.nameMatchesFile) rows.push(['name', `${memory.name} (differs from filename)`]);
280
286
  for (const [key, value] of Object.entries(memory.metadata)) {
281
- if (key !== 'type' && key !== 'modified') rows.push([key, value]);
287
+ if (key !== 'type' && key !== 'modified' && key !== 'originSessionId') rows.push([key, value]);
282
288
  }
283
289
  for (const [key, value] of rows) {
284
290
  meta.append(node('dt', { class: ui.metaKey, text: key }), node('dd', { class: ui.metaValue, text: String(value) }));
285
291
  }
286
292
  card.append(meta);
287
293
 
294
+ const origin = provenanceLine(memory);
295
+ if (origin) card.append(origin);
296
+
288
297
  const body = node('div', { class: ui.prose });
289
298
  card.append(body);
290
299
  renderBody(body, memory, state.store);
@@ -863,6 +872,32 @@ function renderIssue(item, memories) {
863
872
  );
864
873
  }
865
874
 
875
+ if (item.kind === 'provenance-expired') {
876
+ return issue(
877
+ `Provenance gone: ${item.name}`,
878
+ `the session it was written in (${item.sessionId}) has been swept, so why this memory exists can no longer be traced. Nothing recovers it - judge the memory on what it says.`,
879
+ { bad, action: { label: 'Open', run: () => selectMemory(item.file) } },
880
+ );
881
+ }
882
+
883
+ if (item.kind === 'path-evidence-expiring') {
884
+ return issue(
885
+ item.days === 0
886
+ ? 'The last transcript naming this project is due to be swept'
887
+ : `The last transcript naming this project is swept in ${item.days} days`,
888
+ `${item.path} is recovered from the session transcripts, and Claude Code drops those after ${item.retentionDays} days while never touching memory/. Once the last one goes this store shows its raw folder name instead. Remember path records it now.`,
889
+ { bad, action: { label: 'Remember path\u2026', run: openRememberPathDialog } },
890
+ );
891
+ }
892
+
893
+ if (item.kind === 'no-memory-despite-sessions') {
894
+ return issue(
895
+ `${item.sessionCount} ${item.sessionCount === 1 ? 'session' : 'sessions'}, and no memory written`,
896
+ `auto memory is on for this project, but nothing has been saved in the ${item.retentionDays} days of transcripts kept here. Either nothing was worth remembering, or memory is not being written.`,
897
+ { bad },
898
+ );
899
+ }
900
+
866
901
  return issue(item.kind, JSON.stringify(item));
867
902
  }
868
903
 
@@ -887,6 +922,157 @@ const SETTINGS_PROBLEMS = {
887
922
 
888
923
  const show = (value) => (value === undefined ? 'unset' : JSON.stringify(value));
889
924
 
925
+ const TITLE_SOURCE = {
926
+ 'ai-title': 'title Claude Code generated for this session',
927
+ slug: 'session slug, no generated title found in the part read',
928
+ prompt: 'first prompt, no generated title found in the part read',
929
+ };
930
+
931
+ const sizeLabel = (bytes) => (bytes >= 1048576
932
+ ? `${(bytes / 1048576).toFixed(1)} MB`
933
+ : `${(bytes / 1024).toFixed(1)} KB`);
934
+
935
+ function sessionDate(iso) {
936
+ return iso ? iso.replace('T', ' ').replace(/\..*$/, '') : 'unknown';
937
+ }
938
+
939
+ function sessionRow(session, memoriesFrom) {
940
+ const soon = session.expiresInDays !== null && session.expiresInDays <= 7;
941
+ const caret = node('span', { class: ui.contextCaret, text: '\u25b8' });
942
+
943
+ const tags = [];
944
+ if (session.gitBranch) tags.push(node('span', { class: ui.badge('neutral'), text: session.gitBranch }));
945
+ if (session.model) tags.push(node('span', { class: ui.badge('neutral'), text: session.model }));
946
+ if (session.expiresInDays !== null) {
947
+ tags.push(node('span', {
948
+ class: ui.badge(soon ? 'warn' : 'neutral'),
949
+ text: session.expiresInDays === 0 ? 'due to be swept' : `swept in ${session.expiresInDays}d`,
950
+ }));
951
+ }
952
+ for (const memory of memoriesFrom) {
953
+ tags.push(node('span', { class: ui.badge('project'), text: memory.name }));
954
+ }
955
+
956
+ const body = node('div', { class: ui.contextBody, hidden: true });
957
+ const facts = node('dl', { class: ui.metaList });
958
+ const rows = [
959
+ ['session', session.id],
960
+ ['last active', `${sessionDate(session.modified)} (file mtime)`],
961
+ ['size', sizeLabel(session.bytes)],
962
+ ];
963
+ if (session.cwd) rows.push(['cwd', session.cwd]);
964
+ if (session.version) rows.push(['version', session.version]);
965
+ if (session.titleFrom) rows.push(['title from', TITLE_SOURCE[session.titleFrom] || session.titleFrom]);
966
+ for (const [key, value] of rows) {
967
+ facts.append(node('dt', { class: ui.metaKey, text: key }), node('dd', { class: ui.metaValue, text: value }));
968
+ }
969
+ body.append(facts);
970
+ if (session.prompt) {
971
+ body.append(node('div', { class: ui.sectionLabel, text: 'Opened with' }));
972
+ body.append(node('p', { class: ui.note, text: session.prompt }));
973
+ }
974
+ if (memoriesFrom.length) {
975
+ body.append(node('div', { class: ui.sectionLabel, text: 'Memories written here' }));
976
+ const chips = node('div', { class: ui.linkRow });
977
+ for (const memory of memoriesFrom) {
978
+ chips.append(node('button', {
979
+ class: ui.chip(true),
980
+ text: memory.name,
981
+ onclick: () => selectMemory(memory.file),
982
+ }));
983
+ }
984
+ body.append(chips);
985
+ }
986
+
987
+ const button = node('button', {
988
+ class: ui.contextRowButton,
989
+ 'aria-expanded': 'false',
990
+ onclick: (event) => {
991
+ const open = body.hidden;
992
+ body.hidden = !open;
993
+ caret.textContent = open ? '\u25be' : '\u25b8';
994
+ event.currentTarget.setAttribute('aria-expanded', open ? 'true' : 'false');
995
+ },
996
+ }, [
997
+ node('div', { class: ui.contextMain }, [
998
+ node('div', { class: ui.contextTags }, tags),
999
+ node('div', { class: ui.contextFile }, [caret, document.createTextNode(session.title || session.id)]),
1000
+ ]),
1001
+ node('div', { class: ui.contextSize, text: sessionDate(session.modified).slice(0, 10) }),
1002
+ ]);
1003
+
1004
+ return node('div', {}, [button, body]);
1005
+ }
1006
+
1007
+ function retentionTrack(sessions, days) {
1008
+ const track = node('div', { class: ui.retentionTrack }, [node('span', { class: ui.retentionSweep })]);
1009
+ for (const session of sessions) {
1010
+ if (session.expiresInDays === null) continue;
1011
+ const share = days > 0 ? Math.min(1, session.expiresInDays / days) : 1;
1012
+ track.append(node('span', {
1013
+ class: ui.retentionTick(session.expiresInDays <= 7),
1014
+ style: `left: ${(share * 100).toFixed(2)}%`,
1015
+ title: `${session.title || session.id}\nswept in ${session.expiresInDays} days`,
1016
+ }));
1017
+ }
1018
+ return node('div', {}, [
1019
+ track,
1020
+ node('div', { class: ui.retentionScale }, [
1021
+ node('span', { text: 'swept' }),
1022
+ node('span', { text: `today · kept ${days} days` }),
1023
+ ]),
1024
+ ]);
1025
+ }
1026
+
1027
+ async function renderSessions(container) {
1028
+ let data = state.aux.sessions;
1029
+ if (!data) {
1030
+ container.append(node('p', { class: ui.note, text: 'Reading session transcripts\u2026' }));
1031
+ try {
1032
+ data = await api(`/api/stores/${encodeURIComponent(state.storeId)}/sessions`);
1033
+ } catch (err) {
1034
+ container.textContent = '';
1035
+ return container.append(node('p', { class: ui.note, text: err.message }));
1036
+ }
1037
+ if (state.tab !== 'sessions') return;
1038
+ state.aux.sessions = data;
1039
+ }
1040
+ container.textContent = '';
1041
+
1042
+ const byOrigin = new Map();
1043
+ for (const memory of state.store.memories) {
1044
+ if (!memory.origin?.present) continue;
1045
+ if (!byOrigin.has(memory.origin.sessionId)) byOrigin.set(memory.origin.sessionId, []);
1046
+ byOrigin.get(memory.origin.sessionId).push(memory);
1047
+ }
1048
+
1049
+ container.append(node('p', { class: ui.noteTight, text: 'The transcripts sitting beside this store\u2019s memory. Claude Code deletes them once they pass the retention period and never touches memory/, so this is the evidence that expires while the memories stay. This tab only reads, and only the head of each file - nothing here deletes a transcript.' }));
1050
+
1051
+ container.append(node('div', { class: ui.meter }, [
1052
+ node('div', { class: ui.meterTop }, [
1053
+ node('span', { class: ui.meterValue, text: String(data.count) }),
1054
+ node('span', { class: ui.meterUnit, text: data.count === 1 ? 'session kept' : 'sessions kept' }),
1055
+ ]),
1056
+ retentionTrack(data.sessions, data.days),
1057
+ node('div', { class: ui.meterFacts }, [
1058
+ node('span', {}, [node('b', { class: ui.meterFactValue, text: sizeLabel(data.bytes) }), document.createTextNode(' on disk')]),
1059
+ node('span', {}, [node('b', { class: ui.meterFactValue, text: `${data.days} days` }), document.createTextNode(' retention (cleanupPeriodDays)')]),
1060
+ node('span', {}, [
1061
+ node('b', { class: ui.meterFactValue, text: data.evidenceExpiresInDays === null ? '-' : `${data.evidenceExpiresInDays} days` }),
1062
+ document.createTextNode(' until the last proof of this path goes'),
1063
+ ]),
1064
+ ]),
1065
+ node('p', { class: ui.meterNote, text: 'Sweep dates come from each file\u2019s mtime, which any copy or restore resets, so they are an estimate of when Claude Code will drop them rather than something the transcript recorded about itself.' }),
1066
+ ]));
1067
+
1068
+ container.append(node('div', { class: ui.sectionLabel, text: `Sessions \u00b7 ${data.count}` }));
1069
+ const card = node('div', { class: ui.card });
1070
+ for (const session of data.sessions) {
1071
+ card.append(sessionRow(session, byOrigin.get(session.id) || []));
1072
+ }
1073
+ container.append(card);
1074
+ }
1075
+
890
1076
  async function renderSettings(container) {
891
1077
  let data = state.aux.settings;
892
1078
  if (!data) {
@@ -1935,6 +2121,7 @@ function renderTab() {
1935
2121
  if (state.tab === 'memories') renderMemories(container);
1936
2122
  else if (state.tab === 'prune') renderPrune(container);
1937
2123
  else if (state.tab === 'context') renderContext(container);
2124
+ else if (state.tab === 'sessions') renderSessions(container);
1938
2125
  else if (state.tab === 'index') renderIndex(container);
1939
2126
  else if (state.tab === 'health') renderHealth(container);
1940
2127
  else if (state.tab === 'settings') renderSettings(container);
@@ -1987,6 +2174,49 @@ async function sweepIssues() {
1987
2174
  renderStores();
1988
2175
  }
1989
2176
 
2177
+ function provenanceLine(memory) {
2178
+ const origin = memory.origin;
2179
+ if (!origin) return null;
2180
+
2181
+ const known = state.aux.sessions?.sessions.find((session) => session.id === origin.sessionId);
2182
+ const label = origin.present ? (known?.title || origin.sessionId) : origin.sessionId;
2183
+
2184
+ const row = node('div', { class: ui.provenanceRow }, [node('span', { text: 'written in' })]);
2185
+
2186
+ if (origin.present) {
2187
+ row.append(node('button', {
2188
+ class: ui.provenanceLink(true),
2189
+ text: label,
2190
+ title: 'Open the Sessions tab',
2191
+ onclick: () => goToTab('sessions'),
2192
+ }));
2193
+ if (known?.gitBranch) row.append(node('span', { text: `on ${known.gitBranch}` }));
2194
+ if (origin.modified) row.append(node('span', { text: sessionDate(origin.modified).slice(0, 10) }));
2195
+ if (!known) loadSessionsForProvenance();
2196
+ } else {
2197
+ row.append(node('span', {
2198
+ class: ui.provenanceLink(false),
2199
+ text: label,
2200
+ title: 'This transcript is no longer on disk',
2201
+ }));
2202
+ row.append(node('span', { text: '- transcript swept, so why this memory exists can no longer be traced' }));
2203
+ }
2204
+ return row;
2205
+ }
2206
+
2207
+ async function loadSessionsForProvenance() {
2208
+ if (state.aux.sessions || !state.store?.sessions?.count) return;
2209
+ const id = state.storeId;
2210
+ try {
2211
+ const data = await api(`/api/stores/${encodeURIComponent(id)}/sessions`);
2212
+ if (state.storeId !== id) return;
2213
+ state.aux.sessions = data;
2214
+ renderTab();
2215
+ } catch {
2216
+ return;
2217
+ }
2218
+ }
2219
+
1990
2220
  function renderStoreHeader() {
1991
2221
  const store = state.store;
1992
2222
  el('project-title').textContent = store.label;
@@ -2029,6 +2259,18 @@ function renderStoreHeader() {
2029
2259
  ]));
2030
2260
  }
2031
2261
 
2262
+ const evidence = store.sessions?.evidenceExpiresInDays;
2263
+ if (!store.sessions?.remembered
2264
+ && (store.resolvedBy === 'transcript' || store.resolvedBy === 'repo-root')
2265
+ && typeof evidence === 'number' && evidence <= 14) {
2266
+ sub.append(node('span', { class: ui.subWarn }, [
2267
+ document.createTextNode(evidence === 0
2268
+ ? 'the last transcript proving this path is due to be swept - the store keeps its memories and loses its name '
2269
+ : `the last transcript proving this path is swept in ${evidence} days, and nothing else records it `),
2270
+ node('button', { class: ui.linkButton, text: 'Remember path\u2026', onclick: openRememberPathDialog }),
2271
+ ]));
2272
+ }
2273
+
2032
2274
  const auto = store.autoMemory;
2033
2275
  if (auto && auto.known && !auto.enabled) {
2034
2276
  sub.append(node('span', { class: ui.subWarn, text: auto.scope === 'env'
@@ -2040,7 +2282,7 @@ function renderStoreHeader() {
2040
2282
  async function openStore(id, { keepTab = false } = {}) {
2041
2283
  if (id !== state.storeId) state.pruneSelection.clear();
2042
2284
  state.storeId = id;
2043
- state.aux = { instructions: null, settings: null };
2285
+ state.aux = { instructions: null, settings: null, sessions: null };
2044
2286
  state.pathCheck = null;
2045
2287
  if (!keepTab) {
2046
2288
  const opening = state.stores.find((store) => store.id === id);
package/public/styles.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;--font-mono:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;--color-red-50:oklch(97.1% .013 17.4);--color-red-300:oklch(80.8% .114 19.6);--color-red-400:oklch(70.4% .16 22.2);--color-red-500:oklch(63.7% .208 25.3);--color-red-600:oklch(57.7% .215 27.3);--color-red-950:oklch(25.8% .092 26);--color-amber-50:oklch(97.7% .021 84.6);--color-amber-300:oklch(87.9% .153 91.6);--color-amber-400:oklch(82.8% .171 84.4);--color-amber-600:oklch(66.6% .15 58.3);--color-amber-700:oklch(55.5% .13 49);--color-amber-950:oklch(27.9% .077 45.6);--color-green-50:oklch(98.2% .018 155.8);--color-green-300:oklch(87.1% .12 154.4);--color-green-400:oklch(79.2% .15 151.7);--color-green-600:oklch(58.5% .145 149.2);--color-green-700:oklch(52.7% .132 150.1);--color-green-950:oklch(26.6% .065 152.9);--color-blue-50:oklch(97% .014 253.3);--color-blue-200:oklch(88% .06 253.3);--color-blue-300:oklch(78.4% .124 239.1);--color-blue-400:oklch(75.2% .138 243.7);--color-blue-600:oklch(56.5% .163 253.3);--color-blue-700:oklch(48.5% .15 253.3);--color-blue-800:oklch(41% .125 253.3);--color-blue-950:oklch(24% .07 253.3);--color-violet-50:oklch(96.9% .016 300);--color-violet-300:oklch(82.7% .104 300);--color-violet-700:oklch(49.6% .19 300);--color-violet-950:oklch(28.3% .109 300);--color-slate-50:oklch(98.4% .003 253.3);--color-slate-100:oklch(96.4% .005 253.3);--color-slate-200:oklch(92.6% .009 253.3);--color-slate-300:oklch(86.6% .014 253.3);--color-slate-400:oklch(70.4% .021 253.3);--color-slate-500:oklch(55.6% .026 253.3);--color-slate-600:oklch(44.8% .028 253.3);--color-slate-700:oklch(36.8% .028 253.3);--color-slate-800:oklch(26.4% .026 253.3);--color-slate-900:oklch(20.4% .024 253.3);--color-slate-950:oklch(14.2% .022 253.3);--spacing:.25rem;--container-2xl:42rem;--text-xs:.6875rem;--text-xs--line-height:1.45;--text-sm:.8125rem;--text-sm--line-height:1.5;--text-base:.875rem;--text-base--line-height:1.6;--text-lg:1rem;--text-lg--line-height:1.5;--text-xl:1.25rem;--text-xl--line-height:1.3;--text-2xl:1.5rem;--text-2xl--line-height:1.2;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-widest:.12em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--default-transition-duration:.12s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-slate-0:oklch(100% 0 253.3);--spacing-control-x-sm:calc(var(--spacing) * 2);--spacing-control-y-sm:calc(var(--spacing) * 1);--spacing-control-x-md:calc(var(--spacing) * 3);--spacing-control-y-md:calc(var(--spacing) * 2);--spacing-control-x-lg:calc(var(--spacing) * 3.5);--spacing-control-y-lg:calc(var(--spacing) * 2.5);--spacing-control-h-sm:calc(var(--spacing) * 6.5);--spacing-control-h-md:calc(var(--spacing) * 9.5);--spacing-control-h-lg:calc(var(--spacing) * 9.5);--text-2xs:.625rem;--text-2xs--line-height:1.4;--tracking-tightest:-.03em}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{color-scheme:light}:root[data-theme=dark]{color-scheme:dark}html,body{height:100%}body{background-color:var(--ui-canvas);color:var(--ui-fg);font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--text-base--line-height)}::selection{background-color:var(--ui-accent-surface);color:var(--ui-accent)}:focus-visible{outline:2px solid var(--ui-accent);outline-offset:1px}}@layer components{.prose{overflow-wrap:anywhere}.prose>:first-child{margin-top:0}.prose>:last-child{margin-bottom:0}.prose h1,.prose h2,.prose h3,.prose h4{margin:calc(var(--spacing) * 6) 0 calc(var(--spacing) * 2);letter-spacing:var(--tracking-tightest);font-weight:650;line-height:1.25}.prose h1{font-size:var(--text-lg)}.prose h2{font-size:var(--text-base);padding-bottom:calc(var(--spacing) * 1.5);border-bottom:1px solid var(--ui-line)}.prose h3,.prose h4{font-size:var(--text-base);color:var(--ui-fg-muted)}.prose p{margin:calc(var(--spacing) * 3) 0}.prose ul,.prose ol{margin:calc(var(--spacing) * 3) 0;padding-left:calc(var(--spacing) * 5)}.prose ul{list-style-type:"– "}.prose ol{list-style-type:decimal}.prose li{margin:calc(var(--spacing) * 1) 0}.prose li::marker{color:var(--ui-accent);font-family:var(--font-mono)}.prose li>ul,.prose li>ol{margin:calc(var(--spacing) * 1) 0}.prose a{color:var(--ui-accent);text-underline-offset:2px;text-decoration:underline}.prose a:hover{color:var(--ui-accent-hover)}.prose strong{color:var(--ui-fg);font-weight:650}.prose pre{margin:calc(var(--spacing) * 3) 0;padding:calc(var(--spacing) * 3);border:1px solid var(--ui-line);border-radius:var(--radius-md);background-color:var(--ui-surface-sunken);font-size:var(--text-xs);line-height:1.6;overflow-x:auto}.prose code{font-family:var(--font-mono);font-size:.9em}.prose :not(pre)>code{border:1px solid var(--ui-line);border-radius:var(--radius-sm);background-color:var(--ui-surface-sunken);color:var(--ui-accent);padding:1px 4px}.prose blockquote{margin:calc(var(--spacing) * 3) 0;padding-left:calc(var(--spacing) * 3);border-left:2px solid var(--ui-accent-line);color:var(--ui-fg-muted)}.prose hr{margin:calc(var(--spacing) * 5) 0;border:0;border-top:1px dashed var(--ui-line-strong)}.prose table{margin:calc(var(--spacing) * 3) 0;border-collapse:collapse;font-size:var(--text-sm);display:block;overflow-x:auto}.prose th,.prose td{padding:calc(var(--spacing) * 1.5) calc(var(--spacing) * 2.5);border:1px solid var(--ui-line);text-align:left}.prose th{background-color:var(--ui-surface-sunken);font-weight:600}.graph svg{cursor:grab;width:100%;display:block}.graph .node circle{cursor:pointer;stroke-width:1.5px}.graph .node circle.hit{fill:#0000;stroke:none}.graph .node text{fill:var(--ui-fg-muted);font-family:var(--font-mono);pointer-events:none;font-size:9px}.graph .node.hi circle{stroke:var(--ui-accent);stroke-width:2.5px}.graph .node.hi text{fill:var(--ui-fg);font-weight:600}.graph .node.dim{opacity:.18}.graph .node.dim text{display:none}.graph .edge{stroke:var(--ui-line-strong)}.graph .edge.dim{opacity:.05}}@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.top-0{top:0}.top-3{top:calc(var(--spacing) * 3)}.top-3\.5{top:calc(var(--spacing) * 3.5)}.right-3\.5{right:calc(var(--spacing) * 3.5)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-5{bottom:calc(var(--spacing) * 5)}.left-0{left:0}.left-1\/2{left:50%}.left-3\.5{left:calc(var(--spacing) * 3.5)}.z-10{z-index:10}.z-20{z-index:20}.z-100{z-index:100}.z-200{z-index:200}.float-right{float:right}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:0}.my-1{margin-block:var(--spacing)}.my-2\.5{margin-block:calc(var(--spacing) * 2.5)}.mt-0{margin-top:0}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.-mb-px{margin-bottom:-1px}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-2\.5{margin-bottom:calc(var(--spacing) * 2.5)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-3\.5{margin-bottom:calc(var(--spacing) * 3.5)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.h-2{height:calc(var(--spacing) * 2)}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[28rem\]{max-height:28rem}.max-h-\[82vh\]{max-height:82vh}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-control-h-lg{min-height:var(--spacing-control-h-lg)}.min-h-control-h-md{min-height:var(--spacing-control-h-md)}.min-h-control-h-sm{min-height:var(--spacing-control-h-sm)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.min-w-0{min-width:0}.min-w-6{min-width:calc(var(--spacing) * 6)}.min-w-control-h-sm{min-width:var(--spacing-control-h-sm)}.flex-1{flex:1}.flex-none{flex:none}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-default{cursor:default}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\[3rem_1fr\]{grid-template-columns:3rem 1fr}.grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-px{gap:1px}.gap-x-3\.5{column-gap:calc(var(--spacing) * 3.5)}.gap-y-1{row-gap:var(--spacing)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-none{--tw-border-style:none;border-style:none}.border-accent{border-color:var(--ui-accent)}.border-accent-line{border-color:var(--ui-accent-line)}.border-current{border-color:currentColor}.border-danger-line{border-color:var(--ui-danger-line)}.border-line{border-color:var(--ui-line)}.border-transparent{border-color:#0000}.border-l-danger{border-left-color:var(--ui-danger)}.border-l-warn{border-left-color:var(--ui-warn)}.bg-accent{background-color:var(--ui-accent)}.bg-accent-surface{background-color:var(--ui-accent-surface)}.bg-canvas{background-color:var(--ui-canvas)}.bg-danger{background-color:var(--ui-danger)}.bg-danger-surface{background-color:var(--ui-danger-surface)}.bg-fg{background-color:var(--ui-fg)}.bg-line-strong{background-color:var(--ui-line-strong)}.bg-ok{background-color:var(--ui-ok)}.bg-ok-surface{background-color:var(--ui-ok-surface)}.bg-scope-local-surface{background-color:var(--ui-scope-local-surface)}.bg-scope-managed-surface{background-color:var(--ui-scope-managed-surface)}.bg-scope-project-surface{background-color:var(--ui-scope-project-surface)}.bg-scope-user-surface{background-color:var(--ui-scope-user-surface)}.bg-scrim{background-color:var(--ui-scrim)}.bg-surface{background-color:var(--ui-surface)}.bg-surface-sunken{background-color:var(--ui-surface-sunken)}.bg-transparent{background-color:#0000}.bg-type-feedback-surface{background-color:var(--ui-type-feedback-surface)}.bg-type-project-surface{background-color:var(--ui-type-project-surface)}.bg-type-reference-surface{background-color:var(--ui-type-reference-surface)}.bg-type-user-surface{background-color:var(--ui-type-user-surface)}.bg-warn{background-color:var(--ui-warn)}.bg-warn-surface{background-color:var(--ui-warn-surface)}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-10{padding:calc(var(--spacing) * 10)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-10{padding-inline:calc(var(--spacing) * 10)}.px-control-x-lg{padding-inline:var(--spacing-control-x-lg)}.px-control-x-md{padding-inline:var(--spacing-control-x-md)}.px-control-x-sm{padding-inline:var(--spacing-control-x-sm)}.px-px{padding-inline:1px}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-\[15vh\]{padding-block:15vh}.py-control-y-lg{padding-block:var(--spacing-control-y-lg)}.py-control-y-md{padding-block:var(--spacing-control-y-md)}.py-control-y-sm{padding-block:var(--spacing-control-y-sm)}.py-px{padding-block:1px}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-3\.5{padding-top:calc(var(--spacing) * 3.5)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pb-1{padding-bottom:var(--spacing)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-3\.5{padding-bottom:calc(var(--spacing) * 3.5)}.pb-4\.5{padding-bottom:calc(var(--spacing) * 4.5)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-2xs{font-size:var(--text-2xs);line-height:var(--tw-leading,var(--text-2xs--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tightest{--tw-tracking:var(--tracking-tightest);letter-spacing:var(--tracking-tightest)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--ui-accent)}.text-canvas{color:var(--ui-canvas)}.text-danger{color:var(--ui-danger)}.text-fg{color:var(--ui-fg)}.text-fg-muted{color:var(--ui-fg-muted)}.text-fg-subtle{color:var(--ui-fg-subtle)}.text-ok{color:var(--ui-ok)}.text-on-accent{color:var(--ui-on-accent)}.text-scope-local{color:var(--ui-scope-local)}.text-scope-managed{color:var(--ui-scope-managed)}.text-scope-project{color:var(--ui-scope-project)}.text-scope-user{color:var(--ui-scope-user)}.text-type-feedback{color:var(--ui-type-feedback)}.text-type-project{color:var(--ui-type-project)}.text-type-reference{color:var(--ui-type-reference)}.text-type-user{color:var(--ui-type-user)}.text-warn{color:var(--ui-warn)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-danger{-webkit-text-decoration-color:var(--ui-danger);-webkit-text-decoration-color:var(--ui-danger);text-decoration-color:var(--ui-danger)}.decoration-line-strong{-webkit-text-decoration-color:var(--ui-line-strong);-webkit-text-decoration-color:var(--ui-line-strong);text-decoration-color:var(--ui-line-strong)}.accent-accent{accent-color:var(--ui-accent)}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-card{--tw-shadow:var(--ui-shadow-card);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-pop{--tw-shadow:var(--ui-shadow-pop);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-none{-webkit-user-select:none;user-select:none}.placeholder\:text-fg-subtle::placeholder{color:var(--ui-fg-subtle)}.before\:flex-1:before{content:var(--tw-content);flex:1}.before\:border-t:before{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:1px}.before\:border-dashed:before{content:var(--tw-content);--tw-border-style:dashed;border-style:dashed}.before\:border-danger:before{content:var(--tw-content);border-color:var(--ui-danger)}.after\:flex-1:after{content:var(--tw-content);flex:1}.after\:border-t:after{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:1px}.after\:border-dashed:after{content:var(--tw-content);--tw-border-style:dashed;border-style:dashed}.after\:border-danger:after{content:var(--tw-content);border-color:var(--ui-danger)}.first\:mt-0:first-child{margin-top:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:border-accent:hover{border-color:var(--ui-accent)}.hover\:border-accent-hover:hover{border-color:var(--ui-accent-hover)}.hover\:border-accent-line:hover{border-color:var(--ui-accent-line)}.hover\:border-line-strong:hover{border-color:var(--ui-line-strong)}.hover\:bg-accent-hover:hover{background-color:var(--ui-accent-hover)}.hover\:bg-danger:hover{background-color:var(--ui-danger)}.hover\:bg-surface-sunken:hover{background-color:var(--ui-surface-sunken)}.hover\:text-accent-hover:hover{color:var(--ui-accent-hover)}.hover\:text-fg:hover{color:var(--ui-fg)}.hover\:text-on-accent:hover{color:var(--ui-on-accent)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-accent:focus{border-color:var(--ui-accent)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-45:disabled{opacity:.45}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:80rem){.xl\:sticky{position:sticky}.xl\:top-4{top:calc(var(--spacing) * 4)}.xl\:max-h-\[calc\(100vh-9rem\)\]{max-height:calc(100vh - 9rem)}.xl\:grid-cols-\[minmax\(280px\,380px\)_1fr\]{grid-template-columns:minmax(280px,380px) 1fr}.xl\:overflow-y-auto{overflow-y:auto}}.dark\:invert:where([data-theme=dark],[data-theme=dark] *){--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.\[\&\>span\]\:block>span{display:block}}:root{--ui-canvas:var(--color-slate-50);--ui-surface:var(--color-slate-0);--ui-surface-sunken:var(--color-slate-100);--ui-surface-raised:var(--color-slate-0);--ui-line:var(--color-slate-200);--ui-line-strong:var(--color-slate-300);--ui-fg:var(--color-slate-900);--ui-fg-muted:var(--color-slate-500);--ui-fg-subtle:var(--color-slate-400);--ui-accent:var(--color-blue-600);--ui-accent-hover:var(--color-blue-700);--ui-accent-surface:var(--color-blue-50);--ui-accent-line:var(--color-blue-200);--ui-on-accent:var(--color-slate-0);--ui-danger:var(--color-red-600);--ui-danger-surface:var(--color-red-50);--ui-danger-line:var(--color-red-300);--ui-warn:var(--color-amber-700);--ui-warn-surface:var(--color-amber-50);--ui-warn-line:var(--color-amber-300);--ui-ok:var(--color-green-600);--ui-ok-surface:var(--color-green-50);--ui-ok-line:var(--color-green-300);--ui-type-project:var(--color-blue-700);--ui-type-project-surface:var(--color-blue-50);--ui-type-feedback:var(--color-amber-700);--ui-type-feedback-surface:var(--color-amber-50);--ui-type-user:var(--color-violet-700);--ui-type-user-surface:var(--color-violet-50);--ui-type-reference:var(--color-green-700);--ui-type-reference-surface:var(--color-green-50);--ui-scope-managed:var(--color-red-600);--ui-scope-managed-surface:var(--color-red-50);--ui-scope-user:var(--color-blue-700);--ui-scope-user-surface:var(--color-blue-50);--ui-scope-project:var(--color-amber-700);--ui-scope-project-surface:var(--color-amber-50);--ui-scope-local:var(--color-slate-600);--ui-scope-local-surface:var(--color-slate-100);--ui-shadow-card:0 1px 2px #0000000d;--ui-shadow-pop:0 1px 2px #0000000f, 0 12px 32px #0000001a;--ui-scrim:#0f172a73}:root[data-theme=dark]{--ui-canvas:var(--color-slate-950);--ui-surface:var(--color-slate-900);--ui-surface-sunken:var(--color-slate-950);--ui-surface-raised:var(--color-slate-800);--ui-line:var(--color-slate-800);--ui-line-strong:var(--color-slate-700);--ui-fg:var(--color-slate-100);--ui-fg-muted:var(--color-slate-400);--ui-fg-subtle:var(--color-slate-500);--ui-accent:var(--color-blue-400);--ui-accent-hover:var(--color-blue-300);--ui-accent-surface:var(--color-blue-950);--ui-accent-line:var(--color-blue-800);--ui-on-accent:var(--color-slate-950);--ui-danger:var(--color-red-400);--ui-danger-surface:var(--color-red-950);--ui-danger-line:var(--color-red-500);--ui-warn:var(--color-amber-400);--ui-warn-surface:var(--color-amber-950);--ui-warn-line:var(--color-amber-600);--ui-ok:var(--color-green-400);--ui-ok-surface:var(--color-green-950);--ui-ok-line:var(--color-green-600);--ui-type-project:var(--color-blue-300);--ui-type-project-surface:var(--color-blue-950);--ui-type-feedback:var(--color-amber-300);--ui-type-feedback-surface:var(--color-amber-950);--ui-type-user:var(--color-violet-300);--ui-type-user-surface:var(--color-violet-950);--ui-type-reference:var(--color-green-300);--ui-type-reference-surface:var(--color-green-950);--ui-scope-managed:var(--color-red-400);--ui-scope-managed-surface:var(--color-red-950);--ui-scope-user:var(--color-blue-300);--ui-scope-user-surface:var(--color-blue-950);--ui-scope-project:var(--color-amber-300);--ui-scope-project-surface:var(--color-amber-950);--ui-scope-local:var(--color-slate-400);--ui-scope-local-surface:var(--color-slate-800);--ui-shadow-card:0 1px 2px #0006;--ui-shadow-pop:0 1px 2px #00000080, 0 12px 32px #00000073;--ui-scrim:#020610b3}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;--font-mono:ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;--color-red-50:oklch(97.1% .013 17.4);--color-red-300:oklch(80.8% .114 19.6);--color-red-400:oklch(70.4% .16 22.2);--color-red-500:oklch(63.7% .208 25.3);--color-red-600:oklch(57.7% .215 27.3);--color-red-950:oklch(25.8% .092 26);--color-amber-50:oklch(97.7% .021 84.6);--color-amber-300:oklch(87.9% .153 91.6);--color-amber-400:oklch(82.8% .171 84.4);--color-amber-600:oklch(66.6% .15 58.3);--color-amber-700:oklch(55.5% .13 49);--color-amber-950:oklch(27.9% .077 45.6);--color-green-50:oklch(98.2% .018 155.8);--color-green-300:oklch(87.1% .12 154.4);--color-green-400:oklch(79.2% .15 151.7);--color-green-600:oklch(58.5% .145 149.2);--color-green-700:oklch(52.7% .132 150.1);--color-green-950:oklch(26.6% .065 152.9);--color-blue-50:oklch(97% .014 253.3);--color-blue-200:oklch(88% .06 253.3);--color-blue-300:oklch(78.4% .124 239.1);--color-blue-400:oklch(75.2% .138 243.7);--color-blue-600:oklch(56.5% .163 253.3);--color-blue-700:oklch(48.5% .15 253.3);--color-blue-800:oklch(41% .125 253.3);--color-blue-950:oklch(24% .07 253.3);--color-violet-50:oklch(96.9% .016 300);--color-violet-300:oklch(82.7% .104 300);--color-violet-700:oklch(49.6% .19 300);--color-violet-950:oklch(28.3% .109 300);--color-slate-50:oklch(98.4% .003 253.3);--color-slate-100:oklch(96.4% .005 253.3);--color-slate-200:oklch(92.6% .009 253.3);--color-slate-300:oklch(86.6% .014 253.3);--color-slate-400:oklch(70.4% .021 253.3);--color-slate-500:oklch(55.6% .026 253.3);--color-slate-600:oklch(44.8% .028 253.3);--color-slate-700:oklch(36.8% .028 253.3);--color-slate-800:oklch(26.4% .026 253.3);--color-slate-900:oklch(20.4% .024 253.3);--color-slate-950:oklch(14.2% .022 253.3);--spacing:.25rem;--container-2xl:42rem;--text-xs:.6875rem;--text-xs--line-height:1.45;--text-sm:.8125rem;--text-sm--line-height:1.5;--text-base:.875rem;--text-base--line-height:1.6;--text-lg:1rem;--text-lg--line-height:1.5;--text-xl:1.25rem;--text-xl--line-height:1.3;--text-2xl:1.5rem;--text-2xl--line-height:1.2;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-widest:.12em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--default-transition-duration:.12s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-slate-0:oklch(100% 0 253.3);--spacing-control-x-sm:calc(var(--spacing) * 2);--spacing-control-y-sm:calc(var(--spacing) * 1);--spacing-control-x-md:calc(var(--spacing) * 3);--spacing-control-y-md:calc(var(--spacing) * 2);--spacing-control-x-lg:calc(var(--spacing) * 3.5);--spacing-control-y-lg:calc(var(--spacing) * 2.5);--spacing-control-h-sm:calc(var(--spacing) * 6.5);--spacing-control-h-md:calc(var(--spacing) * 9.5);--spacing-control-h-lg:calc(var(--spacing) * 9.5);--text-2xs:.625rem;--text-2xs--line-height:1.4;--tracking-tightest:-.03em}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{color-scheme:light}:root[data-theme=dark]{color-scheme:dark}html,body{height:100%}body{background-color:var(--ui-canvas);color:var(--ui-fg);font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--text-base--line-height)}::selection{background-color:var(--ui-accent-surface);color:var(--ui-accent)}:focus-visible{outline:2px solid var(--ui-accent);outline-offset:1px}}@layer components{.prose{overflow-wrap:anywhere}.prose>:first-child{margin-top:0}.prose>:last-child{margin-bottom:0}.prose h1,.prose h2,.prose h3,.prose h4{margin:calc(var(--spacing) * 6) 0 calc(var(--spacing) * 2);letter-spacing:var(--tracking-tightest);font-weight:650;line-height:1.25}.prose h1{font-size:var(--text-lg)}.prose h2{font-size:var(--text-base);padding-bottom:calc(var(--spacing) * 1.5);border-bottom:1px solid var(--ui-line)}.prose h3,.prose h4{font-size:var(--text-base);color:var(--ui-fg-muted)}.prose p{margin:calc(var(--spacing) * 3) 0}.prose ul,.prose ol{margin:calc(var(--spacing) * 3) 0;padding-left:calc(var(--spacing) * 5)}.prose ul{list-style-type:"– "}.prose ol{list-style-type:decimal}.prose li{margin:calc(var(--spacing) * 1) 0}.prose li::marker{color:var(--ui-accent);font-family:var(--font-mono)}.prose li>ul,.prose li>ol{margin:calc(var(--spacing) * 1) 0}.prose a{color:var(--ui-accent);text-underline-offset:2px;text-decoration:underline}.prose a:hover{color:var(--ui-accent-hover)}.prose strong{color:var(--ui-fg);font-weight:650}.prose pre{margin:calc(var(--spacing) * 3) 0;padding:calc(var(--spacing) * 3);border:1px solid var(--ui-line);border-radius:var(--radius-md);background-color:var(--ui-surface-sunken);font-size:var(--text-xs);line-height:1.6;overflow-x:auto}.prose code{font-family:var(--font-mono);font-size:.9em}.prose :not(pre)>code{border:1px solid var(--ui-line);border-radius:var(--radius-sm);background-color:var(--ui-surface-sunken);color:var(--ui-accent);padding:1px 4px}.prose blockquote{margin:calc(var(--spacing) * 3) 0;padding-left:calc(var(--spacing) * 3);border-left:2px solid var(--ui-accent-line);color:var(--ui-fg-muted)}.prose hr{margin:calc(var(--spacing) * 5) 0;border:0;border-top:1px dashed var(--ui-line-strong)}.prose table{margin:calc(var(--spacing) * 3) 0;border-collapse:collapse;font-size:var(--text-sm);display:block;overflow-x:auto}.prose th,.prose td{padding:calc(var(--spacing) * 1.5) calc(var(--spacing) * 2.5);border:1px solid var(--ui-line);text-align:left}.prose th{background-color:var(--ui-surface-sunken);font-weight:600}.graph svg{cursor:grab;width:100%;display:block}.graph .node circle{cursor:pointer;stroke-width:1.5px}.graph .node circle.hit{fill:#0000;stroke:none}.graph .node text{fill:var(--ui-fg-muted);font-family:var(--font-mono);pointer-events:none;font-size:9px}.graph .node.hi circle{stroke:var(--ui-accent);stroke-width:2.5px}.graph .node.hi text{fill:var(--ui-fg);font-weight:600}.graph .node.dim{opacity:.18}.graph .node.dim text{display:none}.graph .edge{stroke:var(--ui-line-strong)}.graph .edge.dim{opacity:.05}}@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-2{top:calc(var(--spacing) * 2)}.top-3{top:calc(var(--spacing) * 3)}.top-3\.5{top:calc(var(--spacing) * 3.5)}.right-3\.5{right:calc(var(--spacing) * 3.5)}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-5{bottom:calc(var(--spacing) * 5)}.left-0{left:0}.left-1\/2{left:50%}.left-3\.5{left:calc(var(--spacing) * 3.5)}.z-10{z-index:10}.z-20{z-index:20}.z-100{z-index:100}.z-200{z-index:200}.float-right{float:right}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:0}.my-1{margin-block:var(--spacing)}.my-2\.5{margin-block:calc(var(--spacing) * 2.5)}.mt-0{margin-top:0}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mr-1\.5{margin-right:calc(var(--spacing) * 1.5)}.-mb-px{margin-bottom:-1px}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-2\.5{margin-bottom:calc(var(--spacing) * 2.5)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-3\.5{margin-bottom:calc(var(--spacing) * 3.5)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.h-2{height:calc(var(--spacing) * 2)}.h-9{height:calc(var(--spacing) * 9)}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[28rem\]{max-height:28rem}.max-h-\[82vh\]{max-height:82vh}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-control-h-lg{min-height:var(--spacing-control-h-lg)}.min-h-control-h-md{min-height:var(--spacing-control-h-md)}.min-h-control-h-sm{min-height:var(--spacing-control-h-sm)}.w-0\.5{width:calc(var(--spacing) * .5)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.min-w-0{min-width:0}.min-w-6{min-width:calc(var(--spacing) * 6)}.min-w-control-h-sm{min-width:var(--spacing-control-h-sm)}.flex-1{flex:1}.flex-none{flex:none}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-default{cursor:default}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\[3rem_1fr\]{grid-template-columns:3rem 1fr}.grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-px{gap:1px}.gap-x-3\.5{column-gap:calc(var(--spacing) * 3.5)}.gap-y-1{row-gap:var(--spacing)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-accent{border-color:var(--ui-accent)}.border-accent-line{border-color:var(--ui-accent-line)}.border-current{border-color:currentColor}.border-danger{border-color:var(--ui-danger)}.border-danger-line{border-color:var(--ui-danger-line)}.border-line{border-color:var(--ui-line)}.border-transparent{border-color:#0000}.border-l-danger{border-left-color:var(--ui-danger)}.border-l-warn{border-left-color:var(--ui-warn)}.bg-accent{background-color:var(--ui-accent)}.bg-accent-surface{background-color:var(--ui-accent-surface)}.bg-canvas{background-color:var(--ui-canvas)}.bg-danger{background-color:var(--ui-danger)}.bg-danger-surface{background-color:var(--ui-danger-surface)}.bg-fg{background-color:var(--ui-fg)}.bg-line-strong{background-color:var(--ui-line-strong)}.bg-ok{background-color:var(--ui-ok)}.bg-ok-surface{background-color:var(--ui-ok-surface)}.bg-scope-local-surface{background-color:var(--ui-scope-local-surface)}.bg-scope-managed-surface{background-color:var(--ui-scope-managed-surface)}.bg-scope-project-surface{background-color:var(--ui-scope-project-surface)}.bg-scope-user-surface{background-color:var(--ui-scope-user-surface)}.bg-scrim{background-color:var(--ui-scrim)}.bg-surface{background-color:var(--ui-surface)}.bg-surface-sunken{background-color:var(--ui-surface-sunken)}.bg-transparent{background-color:#0000}.bg-type-feedback-surface{background-color:var(--ui-type-feedback-surface)}.bg-type-project-surface{background-color:var(--ui-type-project-surface)}.bg-type-reference-surface{background-color:var(--ui-type-reference-surface)}.bg-type-user-surface{background-color:var(--ui-type-user-surface)}.bg-warn{background-color:var(--ui-warn)}.bg-warn-surface{background-color:var(--ui-warn-surface)}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-10{padding:calc(var(--spacing) * 10)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-7{padding-inline:calc(var(--spacing) * 7)}.px-10{padding-inline:calc(var(--spacing) * 10)}.px-control-x-lg{padding-inline:var(--spacing-control-x-lg)}.px-control-x-md{padding-inline:var(--spacing-control-x-md)}.px-control-x-sm{padding-inline:var(--spacing-control-x-sm)}.px-px{padding-inline:1px}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-\[15vh\]{padding-block:15vh}.py-control-y-lg{padding-block:var(--spacing-control-y-lg)}.py-control-y-md{padding-block:var(--spacing-control-y-md)}.py-control-y-sm{padding-block:var(--spacing-control-y-sm)}.py-px{padding-block:1px}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-3\.5{padding-top:calc(var(--spacing) * 3.5)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pb-1{padding-bottom:var(--spacing)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-3\.5{padding-bottom:calc(var(--spacing) * 3.5)}.pb-4\.5{padding-bottom:calc(var(--spacing) * 4.5)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-2xs{font-size:var(--text-2xs);line-height:var(--tw-leading,var(--text-2xs--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tightest{--tw-tracking:var(--tracking-tightest);letter-spacing:var(--tracking-tightest)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--ui-accent)}.text-canvas{color:var(--ui-canvas)}.text-danger{color:var(--ui-danger)}.text-fg{color:var(--ui-fg)}.text-fg-muted{color:var(--ui-fg-muted)}.text-fg-subtle{color:var(--ui-fg-subtle)}.text-ok{color:var(--ui-ok)}.text-on-accent{color:var(--ui-on-accent)}.text-scope-local{color:var(--ui-scope-local)}.text-scope-managed{color:var(--ui-scope-managed)}.text-scope-project{color:var(--ui-scope-project)}.text-scope-user{color:var(--ui-scope-user)}.text-type-feedback{color:var(--ui-type-feedback)}.text-type-project{color:var(--ui-type-project)}.text-type-reference{color:var(--ui-type-reference)}.text-type-user{color:var(--ui-type-user)}.text-warn{color:var(--ui-warn)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-danger{-webkit-text-decoration-color:var(--ui-danger);-webkit-text-decoration-color:var(--ui-danger);text-decoration-color:var(--ui-danger)}.decoration-line-strong{-webkit-text-decoration-color:var(--ui-line-strong);-webkit-text-decoration-color:var(--ui-line-strong);text-decoration-color:var(--ui-line-strong)}.accent-accent{accent-color:var(--ui-accent)}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-card{--tw-shadow:var(--ui-shadow-card);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-pop{--tw-shadow:var(--ui-shadow-pop);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.select-none{-webkit-user-select:none;user-select:none}.placeholder\:text-fg-subtle::placeholder{color:var(--ui-fg-subtle)}.before\:flex-1:before{content:var(--tw-content);flex:1}.before\:border-t:before{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:1px}.before\:border-dashed:before{content:var(--tw-content);--tw-border-style:dashed;border-style:dashed}.before\:border-danger:before{content:var(--tw-content);border-color:var(--ui-danger)}.after\:flex-1:after{content:var(--tw-content);flex:1}.after\:border-t:after{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:1px}.after\:border-dashed:after{content:var(--tw-content);--tw-border-style:dashed;border-style:dashed}.after\:border-danger:after{content:var(--tw-content);border-color:var(--ui-danger)}.first\:mt-0:first-child{margin-top:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:border-accent:hover{border-color:var(--ui-accent)}.hover\:border-accent-hover:hover{border-color:var(--ui-accent-hover)}.hover\:border-accent-line:hover{border-color:var(--ui-accent-line)}.hover\:border-line-strong:hover{border-color:var(--ui-line-strong)}.hover\:bg-accent-hover:hover{background-color:var(--ui-accent-hover)}.hover\:bg-danger:hover{background-color:var(--ui-danger)}.hover\:bg-surface-sunken:hover{background-color:var(--ui-surface-sunken)}.hover\:text-accent-hover:hover{color:var(--ui-accent-hover)}.hover\:text-fg:hover{color:var(--ui-fg)}.hover\:text-on-accent:hover{color:var(--ui-on-accent)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-accent:focus{border-color:var(--ui-accent)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-45:disabled{opacity:.45}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:80rem){.xl\:sticky{position:sticky}.xl\:top-4{top:calc(var(--spacing) * 4)}.xl\:max-h-\[calc\(100vh-9rem\)\]{max-height:calc(100vh - 9rem)}.xl\:grid-cols-\[minmax\(280px\,380px\)_1fr\]{grid-template-columns:minmax(280px,380px) 1fr}.xl\:overflow-y-auto{overflow-y:auto}}.dark\:invert:where([data-theme=dark],[data-theme=dark] *){--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.\[\&\>span\]\:block>span{display:block}}:root{--ui-canvas:var(--color-slate-50);--ui-surface:var(--color-slate-0);--ui-surface-sunken:var(--color-slate-100);--ui-surface-raised:var(--color-slate-0);--ui-line:var(--color-slate-200);--ui-line-strong:var(--color-slate-300);--ui-fg:var(--color-slate-900);--ui-fg-muted:var(--color-slate-500);--ui-fg-subtle:var(--color-slate-400);--ui-accent:var(--color-blue-600);--ui-accent-hover:var(--color-blue-700);--ui-accent-surface:var(--color-blue-50);--ui-accent-line:var(--color-blue-200);--ui-on-accent:var(--color-slate-0);--ui-danger:var(--color-red-600);--ui-danger-surface:var(--color-red-50);--ui-danger-line:var(--color-red-300);--ui-warn:var(--color-amber-700);--ui-warn-surface:var(--color-amber-50);--ui-warn-line:var(--color-amber-300);--ui-ok:var(--color-green-600);--ui-ok-surface:var(--color-green-50);--ui-ok-line:var(--color-green-300);--ui-type-project:var(--color-blue-700);--ui-type-project-surface:var(--color-blue-50);--ui-type-feedback:var(--color-amber-700);--ui-type-feedback-surface:var(--color-amber-50);--ui-type-user:var(--color-violet-700);--ui-type-user-surface:var(--color-violet-50);--ui-type-reference:var(--color-green-700);--ui-type-reference-surface:var(--color-green-50);--ui-scope-managed:var(--color-red-600);--ui-scope-managed-surface:var(--color-red-50);--ui-scope-user:var(--color-blue-700);--ui-scope-user-surface:var(--color-blue-50);--ui-scope-project:var(--color-amber-700);--ui-scope-project-surface:var(--color-amber-50);--ui-scope-local:var(--color-slate-600);--ui-scope-local-surface:var(--color-slate-100);--ui-shadow-card:0 1px 2px #0000000d;--ui-shadow-pop:0 1px 2px #0000000f, 0 12px 32px #0000001a;--ui-scrim:#0f172a73}:root[data-theme=dark]{--ui-canvas:var(--color-slate-950);--ui-surface:var(--color-slate-900);--ui-surface-sunken:var(--color-slate-950);--ui-surface-raised:var(--color-slate-800);--ui-line:var(--color-slate-800);--ui-line-strong:var(--color-slate-700);--ui-fg:var(--color-slate-100);--ui-fg-muted:var(--color-slate-400);--ui-fg-subtle:var(--color-slate-500);--ui-accent:var(--color-blue-400);--ui-accent-hover:var(--color-blue-300);--ui-accent-surface:var(--color-blue-950);--ui-accent-line:var(--color-blue-800);--ui-on-accent:var(--color-slate-950);--ui-danger:var(--color-red-400);--ui-danger-surface:var(--color-red-950);--ui-danger-line:var(--color-red-500);--ui-warn:var(--color-amber-400);--ui-warn-surface:var(--color-amber-950);--ui-warn-line:var(--color-amber-600);--ui-ok:var(--color-green-400);--ui-ok-surface:var(--color-green-950);--ui-ok-line:var(--color-green-600);--ui-type-project:var(--color-blue-300);--ui-type-project-surface:var(--color-blue-950);--ui-type-feedback:var(--color-amber-300);--ui-type-feedback-surface:var(--color-amber-950);--ui-type-user:var(--color-violet-300);--ui-type-user-surface:var(--color-violet-950);--ui-type-reference:var(--color-green-300);--ui-type-reference-surface:var(--color-green-950);--ui-scope-managed:var(--color-red-400);--ui-scope-managed-surface:var(--color-red-950);--ui-scope-user:var(--color-blue-300);--ui-scope-user-surface:var(--color-blue-950);--ui-scope-project:var(--color-amber-300);--ui-scope-project-surface:var(--color-amber-950);--ui-scope-local:var(--color-slate-400);--ui-scope-local-surface:var(--color-slate-800);--ui-shadow-card:0 1px 2px #0006;--ui-shadow-pop:0 1px 2px #00000080, 0 12px 32px #00000073;--ui-scrim:#020610b3}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
package/public/ui.mjs CHANGED
@@ -182,6 +182,21 @@ export const indexLineText = ({ kind = 'text', clickable = false, dropped = fals
182
182
  dropped && 'line-through decoration-line-strong',
183
183
  );
184
184
 
185
+ export const retentionTrack = 'relative mt-1 h-9 overflow-hidden rounded-md border border-line bg-surface-sunken';
186
+ export const retentionSweep = 'absolute inset-y-0 left-0 border-l border-dashed border-danger';
187
+ const retentionTickBase = 'absolute top-2 bottom-2 w-0.5 -translate-x-1/2 rounded-full';
188
+ const retentionTickTone = { soon: 'bg-warn', safe: 'bg-accent' };
189
+ export const retentionTick = (soon) => cx(retentionTickBase, soon ? retentionTickTone.soon : retentionTickTone.safe);
190
+ export const retentionScale = 'mt-1.5 flex justify-between font-mono text-2xs text-fg-subtle';
191
+
192
+ export const provenanceRow = 'mt-0.5 mb-3.5 flex flex-wrap items-baseline gap-1.5 text-xs text-fg-muted';
193
+ const provenanceLinkBase = 'cursor-pointer rounded-sm border border-transparent bg-transparent px-1 font-medium';
194
+ const provenanceLinkState = {
195
+ live: 'bg-accent-surface text-accent hover:border-accent-line',
196
+ dead: 'cursor-help bg-danger-surface text-danger line-through',
197
+ };
198
+ export const provenanceLink = (live) => cx(provenanceLinkBase, live ? provenanceLinkState.live : provenanceLinkState.dead);
199
+
185
200
  export const cutLine = 'my-2.5 flex items-center gap-2.5 before:flex-1 before:border-t before:border-dashed before:border-danger after:flex-1 after:border-t after:border-dashed after:border-danger';
186
201
  export const cutLabel = 'flex-none font-mono text-2xs whitespace-nowrap text-danger';
187
202
 
package/server.mjs CHANGED
@@ -16,6 +16,7 @@ import { listStores } from './src/stores.mjs';
16
16
  import { forgetPath, rememberPath } from './src/pathcache.mjs';
17
17
  import { searchAll } from './src/search.mjs';
18
18
  import { verifyPaths } from './src/pathcheck.mjs';
19
+ import { sessionsWithSummaries, transcriptDir } from './src/sessions.mjs';
19
20
  import {
20
21
  addIndexEntry,
21
22
  addIndexEntryPreview,
@@ -267,6 +268,14 @@ async function handleApi(req, res, url) {
267
268
  return sendJson(res, 200, verifyPaths(projectDir, memories));
268
269
  }
269
270
 
271
+ if (action === 'sessions' && req.method === 'GET') {
272
+ const slugDir = transcriptDir(store);
273
+ if (!slugDir) {
274
+ return sendJson(res, 400, { error: 'This store keeps no session transcripts.' });
275
+ }
276
+ return sendJson(res, 200, sessionsWithSummaries(slugDir, { projectDir: storeProjectDir(store) }));
277
+ }
278
+
270
279
  if (action === 'settings' && req.method === 'GET') {
271
280
  return sendJson(res, 200, settingsReport({ projectDir: storeProjectDir(store) }));
272
281
  }
package/src/checks.mjs CHANGED
@@ -5,6 +5,10 @@ export const VALID_TYPES = ['user', 'feedback', 'project', 'reference'];
5
5
  export const EMPTY_BODY_CHARS = 40;
6
6
  export const EMPTY_INSTRUCTION_CHARS = 10;
7
7
  export const HOOK_ECHO_OVERLAP = 0.9;
8
+ export const EVIDENCE_WARNING_DAYS = 14;
9
+ export const EVIDENCE_CRITICAL_DAYS = 7;
10
+
11
+ const TRANSCRIPT_RESOLVED = new Set(['transcript', 'repo-root']);
8
12
 
9
13
  const VALID_TYPE_SET = new Set(VALID_TYPES);
10
14
 
@@ -169,6 +173,60 @@ export function checkEmptyInstructionFile(files) {
169
173
  return empty;
170
174
  }
171
175
 
176
+ export function checkProvenanceExpired(memories, resolveOrigin) {
177
+ if (typeof resolveOrigin !== 'function') return [];
178
+ return memories
179
+ .map((memory) => ({ memory, sessionId: memory.metadata?.originSessionId }))
180
+ .filter(({ sessionId }) => typeof sessionId === 'string' && sessionId)
181
+ .filter(({ sessionId }) => !resolveOrigin(sessionId))
182
+ .map(({ memory, sessionId }) => ({
183
+ kind: 'provenance-expired',
184
+ severity: 'warn',
185
+ file: memory.file,
186
+ name: memory.name,
187
+ sessionId,
188
+ }));
189
+ }
190
+
191
+ export function checkPathEvidenceExpiring(store, retention, { remembered = false } = {}) {
192
+ if (!store || store.kind !== 'auto' || remembered) return [];
193
+ if (!TRANSCRIPT_RESOLVED.has(store.resolvedBy)) return [];
194
+
195
+ const days = retention?.evidenceExpiresInDays;
196
+ if (typeof days !== 'number' || days > EVIDENCE_WARNING_DAYS) return [];
197
+
198
+ return [{
199
+ kind: 'path-evidence-expiring',
200
+ severity: days <= EVIDENCE_CRITICAL_DAYS ? 'bad' : 'warn',
201
+ path: store.path,
202
+ days,
203
+ retentionDays: retention.days,
204
+ sessionCount: retention.count,
205
+ }];
206
+ }
207
+
208
+ export function checkNoMemoryDespiteSessions(store, memories, retention) {
209
+ if (!store || store.kind !== 'auto' || memories.length) return [];
210
+ if (!store.autoMemory?.enabled || !store.autoMemory?.known) return [];
211
+ if (!retention || !retention.count) return [];
212
+
213
+ return [{
214
+ kind: 'no-memory-despite-sessions',
215
+ severity: 'warn',
216
+ sessionCount: retention.count,
217
+ retentionDays: retention.days,
218
+ }];
219
+ }
220
+
221
+ export function sessionChecks(store, memories, retention, { remembered = false, resolveOrigin = null } = {}) {
222
+ if (!retention) return [];
223
+ return [
224
+ ...checkProvenanceExpired(memories, resolveOrigin),
225
+ ...checkPathEvidenceExpiring(store, retention, { remembered }),
226
+ ...checkNoMemoryDespiteSessions(store, memories, retention),
227
+ ];
228
+ }
229
+
172
230
  export function memoryChecks(memories, index) {
173
231
  return [
174
232
  ...checkDuplicateNames(memories),
package/src/model.mjs CHANGED
@@ -15,7 +15,9 @@ import { parseIndex, parseFrontmatter, extractWikilinks } from './parse.mjs';
15
15
  import { listMemoryFiles, memoryDir, resolveProjectPath, shortLabel } from './projects.mjs';
16
16
  import { autoMemoryState } from './settings.mjs';
17
17
  import { ageInDays, estimateTokens, findDuplicates, indexStats } from './stats.mjs';
18
- import { memoryChecks } from './checks.mjs';
18
+ import { memoryChecks, sessionChecks } from './checks.mjs';
19
+ import { listSessions, originSession, retention, transcriptDir } from './sessions.mjs';
20
+ import { rememberedPath } from './pathcache.mjs';
19
21
 
20
22
  export const TRASH_DIR = '.trash';
21
23
 
@@ -95,9 +97,30 @@ function buildResolver(memories) {
95
97
  return (target) => byName.get(target) || byStem.get(target) || null;
96
98
  }
97
99
 
100
+ function provenance(memory, resolveOrigin) {
101
+ const sessionId = memory.metadata?.originSessionId;
102
+ if (typeof sessionId !== 'string' || !sessionId) return null;
103
+ const session = resolveOrigin ? resolveOrigin(sessionId) : null;
104
+ return { sessionId, present: Boolean(session), modified: session ? session.modified : null };
105
+ }
106
+
107
+ function sessionContext(store) {
108
+ const slugDir = transcriptDir(store);
109
+ if (!slugDir) return { slugDir: null, retention: null, resolveOrigin: null, remembered: false };
110
+
111
+ const projectDir = store.pathExists ? store.path : null;
112
+ return {
113
+ slugDir,
114
+ retention: retention(listSessions(slugDir), { projectDir }),
115
+ resolveOrigin: (sessionId) => originSession(slugDir, sessionId),
116
+ remembered: Boolean(store.slug && rememberedPath(store.slug)),
117
+ };
118
+ }
119
+
98
120
  export function buildStore(store) {
99
121
  const { dir } = store;
100
122
  const hasMemoryDir = fs.existsSync(dir);
123
+ const sessions = sessionContext(store);
101
124
 
102
125
  const indexRaw = readIfExists(path.join(dir, 'MEMORY.md'));
103
126
  const index = indexRaw === null ? null : parseIndex(indexRaw);
@@ -139,6 +162,7 @@ export function buildStore(store) {
139
162
  : referencedFiles.has(memory.file)
140
163
  ? 'referenced'
141
164
  : 'orphan';
165
+ memory.origin = provenance(memory, sessions.resolveOrigin);
142
166
  }
143
167
 
144
168
  const existingFiles = new Set(files);
@@ -175,6 +199,10 @@ export function buildStore(store) {
175
199
  ? [{ kind: 'long-hooks', severity: 'warn', count: health.longHooks.length, longest: health.longHooks[0] }]
176
200
  : []),
177
201
  ...memoryChecks(memories, index),
202
+ ...sessionChecks(store, memories, sessions.retention, {
203
+ remembered: sessions.remembered,
204
+ resolveOrigin: sessions.resolveOrigin,
205
+ }),
178
206
  ];
179
207
  health.issues.sort((a, b) => (a.severity === 'bad' ? 0 : 1) - (b.severity === 'bad' ? 0 : 1));
180
208
  health.issueCount = health.issues.length;
@@ -209,6 +237,16 @@ export function buildStore(store) {
209
237
  },
210
238
  duplicates: findDuplicates(memories),
211
239
  trash: listTrash(dir),
240
+ sessions: sessions.retention
241
+ ? {
242
+ count: sessions.retention.count,
243
+ bytes: sessions.retention.bytes,
244
+ retentionDays: sessions.retention.days,
245
+ evidenceExpiresInDays: sessions.retention.evidenceExpiresInDays,
246
+ expiringCount: sessions.retention.expiringCount,
247
+ remembered: sessions.remembered,
248
+ }
249
+ : null,
212
250
  };
213
251
  }
214
252
 
@@ -224,6 +262,7 @@ export function buildProject(root, slug) {
224
262
  kind: 'auto',
225
263
  dir: memoryDir(root, slug),
226
264
  path: resolved.path,
265
+ pathExists: resolved.exists,
227
266
  label: shortLabel(resolved.path),
228
267
  resolvedBy: resolved.resolvedBy,
229
268
  // The decode that could not be confirmed, offered as a starting point when
package/src/projects.mjs CHANGED
@@ -51,12 +51,16 @@ export function projectsRoot() {
51
51
  return resolveRoot().path;
52
52
  }
53
53
 
54
+ // The escalation ladder for reading the head of a transcript: the small read
55
+ // almost always suffices and the large one is the fallback.
56
+ export const HEAD_READS = [16384, 131072];
57
+
54
58
  /**
55
59
  * Read the head of a file without pulling a multi-megabyte transcript into
56
60
  * memory. The `cwd` field shows up within the first few lines in practice, so
57
61
  * the small read almost always suffices and the large one is a fallback.
58
62
  */
59
- function readHead(file, bytes) {
63
+ export function readHead(file, bytes) {
60
64
  const fd = fs.openSync(file, 'r');
61
65
  try {
62
66
  const buf = Buffer.alloc(bytes);
@@ -90,7 +94,7 @@ function findTranscripts(dir, depth = 2) {
90
94
 
91
95
  /** The first `cwd` in one transcript, escalating the read only if the small one missed. */
92
96
  function cwdInTranscript(file) {
93
- for (const size of [16384, 131072]) {
97
+ for (const size of HEAD_READS) {
94
98
  let head;
95
99
  try {
96
100
  head = readHead(file, size);
@@ -0,0 +1,174 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { HEAD_READS, readHead } from './projects.mjs';
4
+ import { cleanupPeriodDays } from './settings.mjs';
5
+ import { ageInDays } from './stats.mjs';
6
+
7
+ export const EXPIRY_WARNING_DAYS = 7;
8
+ export const PROMPT_TITLE_CHARS = 80;
9
+ export const PROMPT_PREVIEW_CHARS = 400;
10
+
11
+ const SESSION_ID = /^[0-9a-fA-F][0-9a-fA-F-]{7,63}$/;
12
+ const SELF_DESCRIBING_KEYS = ['slug', 'gitBranch', 'model', 'version', 'cwd'];
13
+ const WRAPPED_BY_CLAUDE_CODE = /^<(local-command-[a-z]+|command-(message|name|args)|system-reminder)>/;
14
+
15
+ const isSubagentTranscript = (name) => name.startsWith('agent-');
16
+ const isSessionTranscript = (name) => name.endsWith('.jsonl') && !isSubagentTranscript(name);
17
+
18
+ export function listSessions(slugDir) {
19
+ let entries;
20
+ try {
21
+ entries = fs.readdirSync(slugDir, { withFileTypes: true });
22
+ } catch {
23
+ return [];
24
+ }
25
+
26
+ const sessions = [];
27
+ for (const entry of entries) {
28
+ if (!entry.isFile() || !isSessionTranscript(entry.name)) continue;
29
+ const file = path.join(slugDir, entry.name);
30
+ let stat;
31
+ try {
32
+ stat = fs.statSync(file);
33
+ } catch {
34
+ continue;
35
+ }
36
+ sessions.push({
37
+ id: entry.name.replace(/\.jsonl$/, ''),
38
+ file,
39
+ bytes: stat.size,
40
+ modified: new Date(stat.mtimeMs).toISOString(),
41
+ modifiedMs: stat.mtimeMs,
42
+ });
43
+ }
44
+ return sessions.sort((a, b) => b.modifiedMs - a.modifiedMs);
45
+ }
46
+
47
+ function promptText(content) {
48
+ if (typeof content === 'string') return content;
49
+ if (!Array.isArray(content)) return null;
50
+ const joined = content
51
+ .filter((block) => block && block.type === 'text' && typeof block.text === 'string')
52
+ .map((block) => block.text)
53
+ .join(' ');
54
+ return joined || null;
55
+ }
56
+
57
+ function absorbRecord(record, found) {
58
+ if (!record || typeof record !== 'object') return;
59
+
60
+ if (record.type === 'ai-title' && typeof record.aiTitle === 'string' && record.aiTitle) {
61
+ found.aiTitle = record.aiTitle;
62
+ }
63
+ for (const key of SELF_DESCRIBING_KEYS) {
64
+ const value = typeof record[key] === 'string' ? record[key] : record.message?.[key];
65
+ if (found[key] === null && typeof value === 'string' && value) found[key] = value;
66
+ }
67
+ if (found.prompt === null && record.type === 'user' && !record.isSidechain) {
68
+ const text = promptText(record.message?.content)?.trim();
69
+ if (text && !WRAPPED_BY_CLAUDE_CODE.test(text)) found.prompt = text;
70
+ }
71
+ }
72
+
73
+ function absorbChunk(head, found) {
74
+ for (const line of head.split('\n')) {
75
+ if (!line.startsWith('{')) continue;
76
+ try {
77
+ absorbRecord(JSON.parse(line), found);
78
+ } catch {
79
+ continue;
80
+ }
81
+ }
82
+ }
83
+
84
+ function condense(text, chars) {
85
+ const flat = text.replace(/\s+/g, ' ').trim();
86
+ return flat.length > chars ? `${flat.slice(0, chars - 1)}…` : flat;
87
+ }
88
+
89
+ export function sessionSummary(file, { bytes = null } = {}) {
90
+ const found = { aiTitle: null, slug: null, gitBranch: null, model: null, version: null, cwd: null, prompt: null };
91
+
92
+ let size = bytes;
93
+ if (size === null) {
94
+ try {
95
+ size = fs.statSync(file).size;
96
+ } catch {
97
+ size = 0;
98
+ }
99
+ }
100
+
101
+ for (const read of HEAD_READS) {
102
+ let head;
103
+ try {
104
+ head = readHead(file, read);
105
+ } catch {
106
+ break;
107
+ }
108
+ absorbChunk(head, found);
109
+ if (found.aiTitle || size <= read) break;
110
+ }
111
+
112
+ const titleFrom = found.aiTitle ? 'ai-title' : found.slug ? 'slug' : found.prompt ? 'prompt' : null;
113
+ const title = found.aiTitle
114
+ || found.slug
115
+ || (found.prompt ? condense(found.prompt, PROMPT_TITLE_CHARS) : null);
116
+
117
+ return {
118
+ title,
119
+ titleFrom,
120
+ slug: found.slug,
121
+ gitBranch: found.gitBranch,
122
+ model: found.model,
123
+ version: found.version,
124
+ cwd: found.cwd,
125
+ prompt: found.prompt ? condense(found.prompt, PROMPT_PREVIEW_CHARS) : null,
126
+ };
127
+ }
128
+
129
+ export function retention(sessions, { projectDir = null, now = Date.now() } = {}) {
130
+ const days = cleanupPeriodDays(projectDir ? { projectDir } : {});
131
+ const dated = sessions.map((session) => {
132
+ const ageDays = ageInDays(session.modified, now);
133
+ return { ...session, ageDays, expiresInDays: ageDays === null ? null : Math.max(0, days - ageDays) };
134
+ });
135
+ const remaining = dated.map((session) => session.expiresInDays).filter((value) => value !== null);
136
+
137
+ return {
138
+ days,
139
+ dateFrom: 'mtime',
140
+ sessions: dated,
141
+ count: dated.length,
142
+ bytes: dated.reduce((sum, session) => sum + session.bytes, 0),
143
+ evidenceExpiresInDays: remaining.length ? Math.max(...remaining) : null,
144
+ expiringCount: remaining.filter((value) => value <= EXPIRY_WARNING_DAYS).length,
145
+ };
146
+ }
147
+
148
+ export function originSession(slugDir, sessionId) {
149
+ if (typeof sessionId !== 'string' || !SESSION_ID.test(sessionId)) return null;
150
+ const file = path.join(slugDir, `${sessionId}.jsonl`);
151
+ let stat;
152
+ try {
153
+ stat = fs.statSync(file);
154
+ } catch {
155
+ return null;
156
+ }
157
+ if (!stat.isFile()) return null;
158
+ return { id: sessionId, file, bytes: stat.size, modified: new Date(stat.mtimeMs).toISOString() };
159
+ }
160
+
161
+ export function transcriptDir(store) {
162
+ return store && store.kind === 'auto' && store.dir ? path.dirname(store.dir) : null;
163
+ }
164
+
165
+ export function sessionsWithSummaries(slugDir, { projectDir = null, now = Date.now() } = {}) {
166
+ const report = retention(listSessions(slugDir), { projectDir, now });
167
+ return {
168
+ ...report,
169
+ sessions: report.sessions.map((session) => ({
170
+ ...session,
171
+ ...sessionSummary(session.file, { bytes: session.bytes }),
172
+ })),
173
+ };
174
+ }