@adea-ai/ui 0.142.2 → 0.142.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"parse-stack-trace.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/stack-trace/parse-stack-trace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parse-stack-trace.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/stack-trace/parse-stack-trace.ts"],"names":[],"mappings":"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"}
@@ -25,6 +25,31 @@ function splitLocation(location) {
25
25
  line: last.value
26
26
  };
27
27
  }
28
+ /**
29
+ * Split the pre-1.78 Rust panic suffix `'message', location`. The message may
30
+ * contain `', ` itself, and the greedy pattern this replaces took the last
31
+ * occurrence with a non-space behind it — this scan keeps that last-wins
32
+ * answer, in one pass, so a message repeating the marker stays linear.
33
+ */
34
+ function splitRustOldPanic(after) {
35
+ if (/[\n\r\u2028\u2029]/.test(after) || !after.startsWith("'")) return void 0;
36
+ let close;
37
+ let search = 1;
38
+ for (;;) {
39
+ const at = after.indexOf("', ", search);
40
+ if (at < 0) break;
41
+ search = at + 1;
42
+ const start = at + 3;
43
+ if (start >= after.length) continue;
44
+ if (/\s/.test(after[start])) continue;
45
+ close = at;
46
+ }
47
+ if (close === void 0) return void 0;
48
+ return {
49
+ message: after.slice(1, close),
50
+ location: after.slice(close + 3)
51
+ };
52
+ }
28
53
  var FILE_LIKE = /[\\/]|\.[A-Za-z0-9]{1,6}$/;
29
54
  var URL_LIKE = /^node:/i;
30
55
  /** Whether a V8 location is a place in a file rather than a runtime annotation. */
@@ -66,17 +91,54 @@ function parseV8(trimmed) {
66
91
  }
67
92
  return draft;
68
93
  }
69
- var PYTHON_FRAME = /^File "(.*)", line (\d+)(?:, in (.*))?$/;
94
+ var PYTHON_PREFIX = "File \"";
95
+ var PYTHON_LINE = "\", line ";
96
+ var PYTHON_IN = ", in ";
97
+ /** Whether a character is an ASCII digit, as `\d` was in the pattern this replaced. */
98
+ function isDigit(code) {
99
+ return code >= 48 && code <= 57;
100
+ }
101
+ /**
102
+ * Read a Python frame: `File "<file>", line <n>[, in <fn>]`.
103
+ *
104
+ * Scanned, not matched. A line repeating the frame shape many times must still
105
+ * cost one pass, so the closing `", line ` is found by walking its candidates
106
+ * left to right. The last valid candidate wins — what the greedy pattern this
107
+ * replaces would have settled on — so a file path containing a quote still
108
+ * parses exactly as it always did.
109
+ */
70
110
  function parsePython(trimmed) {
71
- const match = PYTHON_FRAME.exec(trimmed);
72
- if (!match) return void 0;
111
+ if (/[\n\r\u2028\u2029]/.test(trimmed) || !trimmed.startsWith(PYTHON_PREFIX)) return void 0;
112
+ let close;
113
+ let digits = 0;
114
+ let rest = 0;
115
+ let hasIn = false;
116
+ let search = 6;
117
+ for (;;) {
118
+ const at = trimmed.indexOf(PYTHON_LINE, search);
119
+ if (at < 0) break;
120
+ search = at + 1;
121
+ let end = at + 8;
122
+ const start = end;
123
+ while (end < trimmed.length && isDigit(trimmed.charCodeAt(end))) end += 1;
124
+ if (end === start) continue;
125
+ if (end < trimmed.length && !trimmed.startsWith(PYTHON_IN, end)) continue;
126
+ close = at;
127
+ digits = end - start;
128
+ rest = end;
129
+ hasIn = end < trimmed.length;
130
+ }
131
+ if (close === void 0) return void 0;
73
132
  const draft = {
74
133
  raw: trimmed,
75
134
  format: "python",
76
- file: match[1],
77
- line: Number(match[2])
135
+ file: trimmed.slice(6, close),
136
+ line: Number(trimmed.slice(rest - digits, rest))
78
137
  };
79
- if (match[3]) draft.function = match[3];
138
+ if (hasIn) {
139
+ const fn = trimmed.slice(rest + 5);
140
+ if (fn) draft.function = fn;
141
+ }
80
142
  return draft;
81
143
  }
82
144
  var RUST_FRAME = /^(\d+):\s+(?:0x[0-9a-fA-F]+ - )?(\S.*)$/;
@@ -239,13 +301,13 @@ function liftHeader(entries, format) {
239
301
  if (at < 0) return void 0;
240
302
  const type = first.slice(0, at).trim();
241
303
  const after = first.slice(at + 4);
242
- const old = /^'(.*)', (\S.*)$/.exec(after);
304
+ const old = splitRustOldPanic(after);
243
305
  let consumed = 1;
244
306
  let message;
245
307
  let location;
246
308
  if (old) {
247
- message = old[1];
248
- location = old[2];
309
+ message = old.message;
310
+ location = old.location;
249
311
  } else {
250
312
  location = after.replace(/:$/, "");
251
313
  const body = [];
@@ -1 +1 @@
1
- {"version":3,"file":"parse-stack-trace.js","names":[],"sources":["../../../../src/components/composites/stack-trace/parse-stack-trace.ts"],"sourcesContent":["/**\n * Stack traces, read without being trusted or trimmed.\n *\n * A trace is text a runtime wrote about itself, and every language writes it\n * differently. This parses the three a coding agent meets most — V8/Node\n * (`at fn (file:line:col)`), Python (`File \"x\", line N, in fn`) and Rust panic\n * backtraces (` 0: symbol` then `at file:line:col`) — into frames, and does\n * three things a formatter would not:\n *\n * - **It never hides a line.** Every input line ends up in `entries`, as a frame\n * or as text, in order. A line the parser does not recognise is text, and a\n * trace it recognises nothing in has `format: 'unknown'` and `frames: []`, so a\n * caller falls back to showing the raw string. The header (error type and\n * message) is lifted out of the entries only when it is unambiguous.\n * - **It makes no claim about whose code a frame is without evidence.** A frame\n * is a dependency when its path says so (`node_modules`, `site-packages`,\n * `.cargo/registry`), internal when it belongs to the runtime (`node:` URLs,\n * Python's frozen modules, Rust's `std`), and first-party otherwise. A host that\n * knows better passes `classify`.\n * - **It is bounded.** Lines over 4 000 characters are never run through a\n * pattern, and input past 10 000 lines is kept as one trailing text entry. Every\n * pattern is anchored and linear, so a hostile trace cannot make parsing slow.\n *\n * Parsing is line-oriented and each line is classified on its own, so a trace\n * that mixes formats (a Node process that shells out to Python) keeps both.\n */\n\nexport type StackFrameKind = 'first-party' | 'dependency' | 'internal'\nexport type StackTraceFormat = 'v8' | 'python' | 'rust' | 'unknown'\n\nexport type StackFrame = Readonly<{\n /** The line exactly as written, minus its line ending. */\n raw: string\n function?: string\n /** The path or URL as written. Undefined for `<anonymous>`, `native` and the like. */\n file?: string\n line?: number\n column?: number\n kind: StackFrameKind\n async?: true\n /** Lines the runtime printed beneath the frame, such as Python's source line. */\n detail?: readonly string[]\n}>\n\nexport type StackEntry =\n | Readonly<{ type: 'frame'; frame: StackFrame }>\n | Readonly<{ type: 'text'; text: string }>\n\nexport type StackTraceHeader = Readonly<{\n /** `TypeError`, `ValueError`, `thread 'main' panicked`. */\n type?: string\n message: string\n /** Where a Rust panic says it happened. */\n location?: string\n}>\n\nexport type ParsedStackTrace = Readonly<{\n format: StackTraceFormat\n header?: StackTraceHeader\n /** Everything that is not the header, in order. */\n entries: readonly StackEntry[]\n frames: readonly StackFrame[]\n}>\n\nexport type ParseStackTraceOptions = {\n /** Override the built-in first-party/dependency/internal guess for a frame. */\n classify?: (frame: StackFrame) => StackFrameKind | undefined\n}\n\nexport const STACK_MAX_LINES = 10_000\nconst LINE_PATTERN_LIMIT = 4000\n\ntype Draft = {\n raw: string\n function?: string\n file?: string\n line?: number\n column?: number\n async?: true\n detail?: string[]\n format: 'v8' | 'python' | 'rust'\n}\n\nfunction trailingNumber(value: string): { value: number; rest: string } | undefined {\n let i = value.length\n while (i > 0 && value.charCodeAt(i - 1) >= 48 && value.charCodeAt(i - 1) <= 57) i -= 1\n if (i === value.length || i === 0 || value[i - 1] !== ':' || value.length - i > 9)\n return undefined\n return { value: Number(value.slice(i)), rest: value.slice(0, i - 1) }\n}\n\n/** Split `path:line:col` from the right, so a Windows drive or a URL port stays in the path. */\nexport function splitLocation(location: string): { file: string; line?: number; column?: number } {\n const last = trailingNumber(location)\n if (!last || last.rest === '') return { file: location }\n const before = trailingNumber(last.rest)\n if (before && before.rest !== '') {\n return { file: before.rest, line: before.value, column: last.value }\n }\n return { file: last.rest, line: last.value }\n}\n\nconst FILE_LIKE = /[\\\\/]|\\.[A-Za-z0-9]{1,6}$/\nconst URL_LIKE = /^node:/i\n\n/** Whether a V8 location is a place in a file rather than a runtime annotation. */\nfunction v8Location(location: string): Partial<Draft> | undefined {\n if (location === '<anonymous>' || location === 'native' || /^index \\d+$/.test(location)) {\n return {}\n }\n if (location.startsWith('eval at ')) return {}\n const split = splitLocation(location)\n if (split.line !== undefined || FILE_LIKE.test(split.file) || URL_LIKE.test(split.file)) {\n return { file: split.file, line: split.line, column: split.column }\n }\n return undefined\n}\n\nfunction parseV8(trimmed: string): Draft | undefined {\n if (!trimmed.startsWith('at ')) return undefined\n const rest = trimmed.slice(3)\n if (rest === '') return undefined\n\n let fn: string | undefined\n let location: string\n if (rest.endsWith(')') && rest.includes(' (')) {\n const open = rest.indexOf(' (')\n fn = rest.slice(0, open)\n location = rest.slice(open + 2, -1)\n } else {\n location = rest\n }\n const parsed = v8Location(location)\n if (!parsed) return undefined\n // Without parentheses the whole remainder was the location, and it must carry\n // a line or a path — otherwise \"at the end of the day\" would be a frame.\n if (\n fn === undefined &&\n parsed.file === undefined &&\n location !== '<anonymous>' &&\n location !== 'native'\n ) {\n return undefined\n }\n\n const draft: Draft = { raw: trimmed, format: 'v8', ...parsed }\n if (fn !== undefined) {\n if (fn.startsWith('async ')) {\n draft.async = true\n fn = fn.slice(6)\n }\n if (fn !== '') draft.function = fn\n }\n return draft\n}\n\nconst PYTHON_FRAME = /^File \"(.*)\", line (\\d+)(?:, in (.*))?$/\n\nfunction parsePython(trimmed: string): Draft | undefined {\n const match = PYTHON_FRAME.exec(trimmed)\n if (!match) return undefined\n const draft: Draft = { raw: trimmed, format: 'python', file: match[1], line: Number(match[2]) }\n if (match[3]) draft.function = match[3]\n return draft\n}\n\nconst RUST_FRAME = /^(\\d+):\\s+(?:0x[0-9a-fA-F]+ - )?(\\S.*)$/\nconst RUST_LOCATION = /^at\\s+(\\S.*)$/\n\nfunction parseRustFrame(trimmed: string, rustContext: boolean): Draft | undefined {\n const match = RUST_FRAME.exec(trimmed)\n if (!match) return undefined\n const symbol = match[2]!\n if (!rustContext && !symbol.includes('::')) return undefined\n return { raw: trimmed, format: 'rust', function: symbol }\n}\n\nconst INTERNAL_RUST_SYMBOLS =\n /^(?:std|core|alloc)::|^<?(?:\\w+ as )?(?:std|core|alloc)::|^(?:rust_begin_unwind|rust_panic|__rust_|_start$|__libc_|_main$)/\n\n/** The built-in guess at whose code a frame is. See the module comment. */\nexport function classifyFrame(\n frame: Omit<StackFrame, 'kind'>,\n format: StackTraceFormat\n): StackFrameKind {\n const file = frame.file?.replaceAll('\\\\', '/')\n if (format === 'rust') {\n if (file !== undefined) {\n if (file.startsWith('/rustc/') || /\\/library\\/(?:std|core|alloc)\\//.test(file))\n return 'internal'\n if (/\\/\\.cargo\\/(?:registry|git)\\/|\\/registry\\/src\\//.test(file)) return 'dependency'\n return 'first-party'\n }\n return frame.function !== undefined && INTERNAL_RUST_SYMBOLS.test(frame.function)\n ? 'internal'\n : 'first-party'\n }\n if (file === undefined) return 'internal'\n if (format === 'python') {\n if (/\\/(?:site|dist)-packages\\//.test(file)) return 'dependency'\n if (\n file.startsWith('<') ||\n /\\/lib\\/python\\d/i.test(file) ||\n /\\/python\\d[\\d.]*\\/lib\\//i.test(file)\n ) {\n return 'internal'\n }\n return 'first-party'\n }\n if (file.startsWith('node:') || file.startsWith('internal/') || file.startsWith('wasm://')) {\n return 'internal'\n }\n if (file.includes('/node_modules/') || file.startsWith('node_modules/')) return 'dependency'\n return 'first-party'\n}\n\nfunction finishFrame(draft: Draft, options: ParseStackTraceOptions | undefined): StackFrame {\n const { format, detail, ...base } = draft\n const shape: Omit<StackFrame, 'kind'> = detail && detail.length > 0 ? { ...base, detail } : base\n // Classified by the frame's own format, so a mixed trace keeps both languages' rules.\n const guess = classifyFrame(shape, format)\n const override = options?.classify?.({ ...shape, kind: guess })\n return { ...shape, kind: override ?? guess }\n}\n\nconst TYPE_AND_MESSAGE = /^([A-Za-z_$][\\w$.]*)(?::\\s?([\\s\\S]*))?$/\n\nfunction splitTypeAndMessage(lines: readonly string[]): StackTraceHeader {\n const first = lines[0] ?? ''\n const match = TYPE_AND_MESSAGE.exec(first)\n const tail = lines.slice(1)\n if (!match) return { message: lines.join('\\n') }\n const message = [match[2] ?? '', ...tail].join('\\n').replace(/^\\n+/, '')\n return { type: match[1], message }\n}\n\n/** Parse a stack trace. Never throws, and every input line appears in the result. */\nexport function parseStackTrace(text: string, options?: ParseStackTraceOptions): ParsedStackTrace {\n const rawLines = text.split('\\n')\n const overflow = rawLines.length > STACK_MAX_LINES ? rawLines.splice(STACK_MAX_LINES) : []\n const rustContext = /panicked at|stack backtrace|RUST_BACKTRACE/.test(text.slice(0, 200_000))\n\n type Item = { type: 'text'; text: string } | { type: 'frame'; draft: Draft }\n const items: Item[] = []\n let python: Draft | undefined\n let rust: Draft | undefined\n\n for (const original of rawLines) {\n const line = original.endsWith('\\r') ? original.slice(0, -1) : original\n const trimmed = line.trim()\n\n if (line.length > LINE_PATTERN_LIMIT || trimmed === '') {\n python = rust = undefined\n items.push({ type: 'text', text: line })\n continue\n }\n\n // Python prints the offending source line, indented, under its frame.\n if (python && /^\\s/.test(line) && !trimmed.startsWith('File \"')) {\n ;(python.detail ??= []).push(line)\n continue\n }\n\n if (rust && rust.file === undefined && RUST_LOCATION.test(trimmed)) {\n const split = splitLocation(RUST_LOCATION.exec(trimmed)![1]!)\n rust.file = split.file\n rust.line = split.line\n rust.column = split.column\n rust.raw = `${rust.raw}\\n${line}`\n continue\n }\n\n const draft = parsePython(trimmed) ?? parseRustFrame(trimmed, rustContext) ?? parseV8(trimmed)\n python = draft?.format === 'python' ? draft : undefined\n rust = draft?.format === 'rust' ? draft : undefined\n if (draft) items.push({ type: 'frame', draft })\n else items.push({ type: 'text', text: line })\n }\n if (overflow.length > 0) items.push({ type: 'text', text: overflow.join('\\n') })\n\n const counts = { v8: 0, python: 0, rust: 0 }\n for (const item of items) if (item.type === 'frame') counts[item.draft.format] += 1\n const total = counts.v8 + counts.python + counts.rust\n const format: StackTraceFormat =\n total === 0\n ? 'unknown'\n : counts.python >= counts.v8 && counts.python >= counts.rust\n ? 'python'\n : counts.rust >= counts.v8\n ? 'rust'\n : 'v8'\n\n const entries: StackEntry[] = items.map((item) =>\n item.type === 'text' ? item : { type: 'frame', frame: finishFrame(item.draft, options) }\n )\n\n if (format === 'unknown') return { format, entries, frames: [] }\n\n const header = liftHeader(entries, format)\n const frames = entries.flatMap((entry) => (entry.type === 'frame' ? [entry.frame] : []))\n return { format, ...(header && { header }), entries, frames }\n}\n\n/**\n * Take the header out of the entries it came from. V8 and Rust print it before\n * the frames; Python prints it after. The lifted lines are removed so they are\n * drawn once, as the header, and nothing else is touched.\n */\nfunction liftHeader(entries: StackEntry[], format: StackTraceFormat): StackTraceHeader | undefined {\n const firstFrame = entries.findIndex((entry) => entry.type === 'frame')\n\n if (format === 'python') {\n let last = entries.length - 1\n while (last >= 0 && (entries[last]!.type !== 'text' || isBlank(entries[last]!))) last -= 1\n let lastFrame = entries.length - 1\n while (lastFrame >= 0 && entries[lastFrame]!.type !== 'frame') lastFrame -= 1\n const entry = entries[last]\n // Only a line shaped like `Type: message` is taken as the header; anything\n // else after the last frame (`[Previous line repeated …]`) stays where it is.\n if (\n !entry ||\n entry.type !== 'text' ||\n last < lastFrame ||\n /^\\s/.test(entry.text) ||\n !TYPE_AND_MESSAGE.test(entry.text.trim())\n ) {\n return undefined\n }\n entries.splice(last, 1)\n return splitTypeAndMessage([entry.text.trim()])\n }\n\n if (format === 'rust') {\n const panicAt = entries.findIndex(\n (entry, index) =>\n index < firstFrame && entry.type === 'text' && /panicked at/.test(entry.text)\n )\n if (panicAt < 0) return undefined\n const first = (entries[panicAt] as { text: string }).text.trim()\n const at = first.indexOf(' at ')\n if (at < 0) return undefined\n const type = first.slice(0, at).trim()\n const after = first.slice(at + 4)\n const old = /^'(.*)', (\\S.*)$/.exec(after)\n let consumed = 1\n let message: string\n let location: string | undefined\n if (old) {\n message = old[1]!\n location = old[2]\n } else {\n location = after.replace(/:$/, '')\n const body: string[] = []\n for (let i = panicAt + 1; i < firstFrame; i += 1) {\n const entry = entries[i]!\n if (\n entry.type !== 'text' ||\n isBlank(entry) ||\n /^(?:note:|stack backtrace:)/.test(entry.text.trim())\n )\n break\n body.push(entry.text)\n consumed += 1\n }\n message = body.join('\\n')\n }\n entries.splice(panicAt, consumed)\n return { type, message, ...(location && { location }) }\n }\n\n // V8: the block of non-blank lines directly above the first frame. Anything\n // above a blank line — Node prints the source line that threw — stays as text.\n if (firstFrame <= 0) return undefined\n let start = firstFrame\n while (start > 0 && entries[start - 1]!.type === 'text' && !isBlank(entries[start - 1]!))\n start -= 1\n if (start === firstFrame) return undefined\n const lines = entries.slice(start, firstFrame).map((entry) => (entry as { text: string }).text)\n entries.splice(start, firstFrame - start)\n return splitTypeAndMessage(lines)\n}\n\nfunction isBlank(entry: StackEntry): boolean {\n return entry.type === 'text' && entry.text.trim() === ''\n}\n\n// ---------------------------------------------------------------------------\n// Grouping\n// ---------------------------------------------------------------------------\n\nexport type StackBlock =\n | Readonly<{ type: 'frame'; frame: StackFrame }>\n | Readonly<{ type: 'text'; text: string }>\n | Readonly<{ type: 'group'; frames: readonly StackFrame[] }>\n\n/**\n * Fold runs of dependency and runtime frames into groups, so a trace reads as\n * the code the reader owns with the framework's frames one click away.\n *\n * A run of one stays a frame — a disclosure around a single line costs more than\n * it saves. When there is no first-party frame at all nothing is grouped:\n * collapsing everything would hide the entire trace behind a button, which is the\n * opposite of what a reader who owns none of the frames needs.\n */\nexport function groupStackEntries(\n entries: readonly StackEntry[],\n collapse = true\n): readonly StackBlock[] {\n const hasFirstParty = entries.some(\n (entry) => entry.type === 'frame' && entry.frame.kind === 'first-party'\n )\n if (!collapse || !hasFirstParty) return entries\n\n const blocks: StackBlock[] = []\n let run: StackFrame[] = []\n const flush = () => {\n if (run.length >= 2) blocks.push({ type: 'group', frames: run })\n else for (const frame of run) blocks.push({ type: 'frame', frame })\n run = []\n }\n for (const entry of entries) {\n if (entry.type === 'frame' && entry.frame.kind !== 'first-party') {\n run.push(entry.frame)\n continue\n }\n flush()\n blocks.push(entry)\n }\n flush()\n return blocks\n}\n\nconst framesNoun = (count: number, word: string) =>\n `${count.toLocaleString()} ${word} ${count === 1 ? 'frame' : 'frames'}`\n\n/** What a collapsed group says it holds. */\nexport function describeFrameGroup(frames: readonly StackFrame[]): string {\n const count = frames.length\n const dependency = frames.filter((frame) => frame.kind === 'dependency').length\n if (dependency === count) return framesNoun(count, 'dependency')\n if (dependency === 0) return framesNoun(count, 'runtime')\n return `${count.toLocaleString()} dependency and runtime frames`\n}\n\n/** `file:line:col`, with whichever parts the frame has. */\nexport function frameLocation(frame: StackFrame): string | undefined {\n if (frame.file === undefined) return undefined\n const column = frame.column === undefined ? '' : `:${frame.column}`\n const line = frame.line === undefined ? '' : `:${frame.line}`\n return `${frame.file}${line}${line ? column : ''}`\n}\n\nconst LEADING_SPACE = /^[ \\t]*/\n\n/** Remove the indentation every non-blank line shares, so a source snippet sits flush. */\nexport function dedentLines(lines: readonly string[]): string[] {\n let indent = Infinity\n for (const line of lines) {\n if (line.trim() !== '') indent = Math.min(indent, LEADING_SPACE.exec(line)![0].length)\n }\n if (indent === Infinity) return [...lines]\n return lines.map((line) => line.slice(Math.min(indent, LEADING_SPACE.exec(line)![0].length)))\n}\n\nexport type BoundedLines = Readonly<{\n lines: string[]\n /** Lines beyond the bound that are not in `lines` at all. */\n omittedLines: number\n /** Characters beyond the bound, in omitted lines and in the cut end of the last shown line. */\n omittedChars: number\n}>\n\n/**\n * The first lines of `lines`, up to `maxLines` of them and `maxChars` characters\n * between them, with what was left out counted rather than dropped silently. It\n * visits the shown lines and adds up the length of the rest, so the cost does not\n * depend on how much text there is to omit.\n */\nexport function boundLines(\n lines: readonly string[],\n maxLines: number,\n maxChars: number\n): BoundedLines {\n const shown: string[] = []\n let chars = 0\n let index = 0\n let partial = 0\n while (index < lines.length && shown.length < maxLines) {\n const line = lines[index]!\n const room = maxChars - chars\n if (line.length <= room) {\n shown.push(line)\n chars += line.length\n index += 1\n continue\n }\n if (room > 0) {\n shown.push(line.slice(0, room))\n partial = line.length - room\n index += 1\n }\n break\n }\n let omittedChars = partial\n for (let rest = index; rest < lines.length; rest += 1) omittedChars += lines[rest]!.length\n return { lines: shown, omittedLines: lines.length - index, omittedChars }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"parse-stack-trace.js","names":[],"sources":["../../../../src/components/composites/stack-trace/parse-stack-trace.ts"],"sourcesContent":["/**\n * Stack traces, read without being trusted or trimmed.\n *\n * A trace is text a runtime wrote about itself, and every language writes it\n * differently. This parses the three a coding agent meets most — V8/Node\n * (`at fn (file:line:col)`), Python (`File \"x\", line N, in fn`) and Rust panic\n * backtraces (` 0: symbol` then `at file:line:col`) — into frames, and does\n * three things a formatter would not:\n *\n * - **It never hides a line.** Every input line ends up in `entries`, as a frame\n * or as text, in order. A line the parser does not recognise is text, and a\n * trace it recognises nothing in has `format: 'unknown'` and `frames: []`, so a\n * caller falls back to showing the raw string. The header (error type and\n * message) is lifted out of the entries only when it is unambiguous.\n * - **It makes no claim about whose code a frame is without evidence.** A frame\n * is a dependency when its path says so (`node_modules`, `site-packages`,\n * `.cargo/registry`), internal when it belongs to the runtime (`node:` URLs,\n * Python's frozen modules, Rust's `std`), and first-party otherwise. A host that\n * knows better passes `classify`.\n * - **It is bounded.** Lines over 4 000 characters are never run through a\n * pattern, and input past 10 000 lines is kept as one trailing text entry. Every\n * pattern is anchored and linear, so a hostile trace cannot make parsing slow.\n *\n * Parsing is line-oriented and each line is classified on its own, so a trace\n * that mixes formats (a Node process that shells out to Python) keeps both.\n */\n\nexport type StackFrameKind = 'first-party' | 'dependency' | 'internal'\nexport type StackTraceFormat = 'v8' | 'python' | 'rust' | 'unknown'\n\nexport type StackFrame = Readonly<{\n /** The line exactly as written, minus its line ending. */\n raw: string\n function?: string\n /** The path or URL as written. Undefined for `<anonymous>`, `native` and the like. */\n file?: string\n line?: number\n column?: number\n kind: StackFrameKind\n async?: true\n /** Lines the runtime printed beneath the frame, such as Python's source line. */\n detail?: readonly string[]\n}>\n\nexport type StackEntry =\n | Readonly<{ type: 'frame'; frame: StackFrame }>\n | Readonly<{ type: 'text'; text: string }>\n\nexport type StackTraceHeader = Readonly<{\n /** `TypeError`, `ValueError`, `thread 'main' panicked`. */\n type?: string\n message: string\n /** Where a Rust panic says it happened. */\n location?: string\n}>\n\nexport type ParsedStackTrace = Readonly<{\n format: StackTraceFormat\n header?: StackTraceHeader\n /** Everything that is not the header, in order. */\n entries: readonly StackEntry[]\n frames: readonly StackFrame[]\n}>\n\nexport type ParseStackTraceOptions = {\n /** Override the built-in first-party/dependency/internal guess for a frame. */\n classify?: (frame: StackFrame) => StackFrameKind | undefined\n}\n\nexport const STACK_MAX_LINES = 10_000\nconst LINE_PATTERN_LIMIT = 4000\n\ntype Draft = {\n raw: string\n function?: string\n file?: string\n line?: number\n column?: number\n async?: true\n detail?: string[]\n format: 'v8' | 'python' | 'rust'\n}\n\nfunction trailingNumber(value: string): { value: number; rest: string } | undefined {\n let i = value.length\n while (i > 0 && value.charCodeAt(i - 1) >= 48 && value.charCodeAt(i - 1) <= 57) i -= 1\n if (i === value.length || i === 0 || value[i - 1] !== ':' || value.length - i > 9)\n return undefined\n return { value: Number(value.slice(i)), rest: value.slice(0, i - 1) }\n}\n\n/** Split `path:line:col` from the right, so a Windows drive or a URL port stays in the path. */\nexport function splitLocation(location: string): { file: string; line?: number; column?: number } {\n const last = trailingNumber(location)\n if (!last || last.rest === '') return { file: location }\n const before = trailingNumber(last.rest)\n if (before && before.rest !== '') {\n return { file: before.rest, line: before.value, column: last.value }\n }\n return { file: last.rest, line: last.value }\n}\n\n/**\n * Split the pre-1.78 Rust panic suffix `'message', location`. The message may\n * contain `', ` itself, and the greedy pattern this replaces took the last\n * occurrence with a non-space behind it — this scan keeps that last-wins\n * answer, in one pass, so a message repeating the marker stays linear.\n */\nfunction splitRustOldPanic(after: string): { message: string; location: string } | undefined {\n // As in `parsePython`: no `.` in the old pattern could cross a line break.\n if (/[\\n\\r\\u2028\\u2029]/.test(after) || !after.startsWith(\"'\")) return undefined\n let close: number | undefined\n let search = 1\n for (;;) {\n const at = after.indexOf(\"', \", search)\n if (at < 0) break\n search = at + 1\n const start = at + 3\n if (start >= after.length) continue\n // Preserve ECMAScript \\S, including NBSP/BOM and Unicode space separators.\n if (/\\s/.test(after[start]!)) continue\n close = at\n }\n if (close === undefined) return undefined\n return { message: after.slice(1, close), location: after.slice(close + 3) }\n}\n\nconst FILE_LIKE = /[\\\\/]|\\.[A-Za-z0-9]{1,6}$/\nconst URL_LIKE = /^node:/i\n\n/** Whether a V8 location is a place in a file rather than a runtime annotation. */\nfunction v8Location(location: string): Partial<Draft> | undefined {\n if (location === '<anonymous>' || location === 'native' || /^index \\d+$/.test(location)) {\n return {}\n }\n if (location.startsWith('eval at ')) return {}\n const split = splitLocation(location)\n if (split.line !== undefined || FILE_LIKE.test(split.file) || URL_LIKE.test(split.file)) {\n return { file: split.file, line: split.line, column: split.column }\n }\n return undefined\n}\n\nfunction parseV8(trimmed: string): Draft | undefined {\n if (!trimmed.startsWith('at ')) return undefined\n const rest = trimmed.slice(3)\n if (rest === '') return undefined\n\n let fn: string | undefined\n let location: string\n if (rest.endsWith(')') && rest.includes(' (')) {\n const open = rest.indexOf(' (')\n fn = rest.slice(0, open)\n location = rest.slice(open + 2, -1)\n } else {\n location = rest\n }\n const parsed = v8Location(location)\n if (!parsed) return undefined\n // Without parentheses the whole remainder was the location, and it must carry\n // a line or a path — otherwise \"at the end of the day\" would be a frame.\n if (\n fn === undefined &&\n parsed.file === undefined &&\n location !== '<anonymous>' &&\n location !== 'native'\n ) {\n return undefined\n }\n\n const draft: Draft = { raw: trimmed, format: 'v8', ...parsed }\n if (fn !== undefined) {\n if (fn.startsWith('async ')) {\n draft.async = true\n fn = fn.slice(6)\n }\n if (fn !== '') draft.function = fn\n }\n return draft\n}\n\nconst PYTHON_PREFIX = 'File \"'\nconst PYTHON_LINE = '\", line '\nconst PYTHON_IN = ', in '\n\n/** Whether a character is an ASCII digit, as `\\d` was in the pattern this replaced. */\nfunction isDigit(code: number): boolean {\n return code >= 48 && code <= 57\n}\n\n/**\n * Read a Python frame: `File \"<file>\", line <n>[, in <fn>]`.\n *\n * Scanned, not matched. A line repeating the frame shape many times must still\n * cost one pass, so the closing `\", line ` is found by walking its candidates\n * left to right. The last valid candidate wins — what the greedy pattern this\n * replaces would have settled on — so a file path containing a quote still\n * parses exactly as it always did.\n */\nfunction parsePython(trimmed: string): Draft | undefined {\n // `.` never matches a line break, so the whole-line pattern this replaces\n // could not have matched a string containing one either.\n if (/[\\n\\r\\u2028\\u2029]/.test(trimmed) || !trimmed.startsWith(PYTHON_PREFIX)) return undefined\n let close: number | undefined\n let digits = 0\n let rest = 0\n let hasIn = false\n let search = PYTHON_PREFIX.length\n for (;;) {\n const at = trimmed.indexOf(PYTHON_LINE, search)\n if (at < 0) break\n search = at + 1\n let end = at + PYTHON_LINE.length\n const start = end\n while (end < trimmed.length && isDigit(trimmed.charCodeAt(end))) end += 1\n if (end === start) continue\n if (end < trimmed.length && !trimmed.startsWith(PYTHON_IN, end)) continue\n close = at\n digits = end - start\n rest = end\n hasIn = end < trimmed.length\n }\n if (close === undefined) return undefined\n const draft: Draft = {\n raw: trimmed,\n format: 'python',\n file: trimmed.slice(PYTHON_PREFIX.length, close),\n line: Number(trimmed.slice(rest - digits, rest)),\n }\n if (hasIn) {\n const fn = trimmed.slice(rest + PYTHON_IN.length)\n if (fn) draft.function = fn\n }\n return draft\n}\n\nconst RUST_FRAME = /^(\\d+):\\s+(?:0x[0-9a-fA-F]+ - )?(\\S.*)$/\nconst RUST_LOCATION = /^at\\s+(\\S.*)$/\n\nfunction parseRustFrame(trimmed: string, rustContext: boolean): Draft | undefined {\n const match = RUST_FRAME.exec(trimmed)\n if (!match) return undefined\n const symbol = match[2]!\n if (!rustContext && !symbol.includes('::')) return undefined\n return { raw: trimmed, format: 'rust', function: symbol }\n}\n\nconst INTERNAL_RUST_SYMBOLS =\n /^(?:std|core|alloc)::|^<?(?:\\w+ as )?(?:std|core|alloc)::|^(?:rust_begin_unwind|rust_panic|__rust_|_start$|__libc_|_main$)/\n\n/** The built-in guess at whose code a frame is. See the module comment. */\nexport function classifyFrame(\n frame: Omit<StackFrame, 'kind'>,\n format: StackTraceFormat\n): StackFrameKind {\n const file = frame.file?.replaceAll('\\\\', '/')\n if (format === 'rust') {\n if (file !== undefined) {\n if (file.startsWith('/rustc/') || /\\/library\\/(?:std|core|alloc)\\//.test(file))\n return 'internal'\n if (/\\/\\.cargo\\/(?:registry|git)\\/|\\/registry\\/src\\//.test(file)) return 'dependency'\n return 'first-party'\n }\n return frame.function !== undefined && INTERNAL_RUST_SYMBOLS.test(frame.function)\n ? 'internal'\n : 'first-party'\n }\n if (file === undefined) return 'internal'\n if (format === 'python') {\n if (/\\/(?:site|dist)-packages\\//.test(file)) return 'dependency'\n if (\n file.startsWith('<') ||\n /\\/lib\\/python\\d/i.test(file) ||\n /\\/python\\d[\\d.]*\\/lib\\//i.test(file)\n ) {\n return 'internal'\n }\n return 'first-party'\n }\n if (file.startsWith('node:') || file.startsWith('internal/') || file.startsWith('wasm://')) {\n return 'internal'\n }\n if (file.includes('/node_modules/') || file.startsWith('node_modules/')) return 'dependency'\n return 'first-party'\n}\n\nfunction finishFrame(draft: Draft, options: ParseStackTraceOptions | undefined): StackFrame {\n const { format, detail, ...base } = draft\n const shape: Omit<StackFrame, 'kind'> = detail && detail.length > 0 ? { ...base, detail } : base\n // Classified by the frame's own format, so a mixed trace keeps both languages' rules.\n const guess = classifyFrame(shape, format)\n const override = options?.classify?.({ ...shape, kind: guess })\n return { ...shape, kind: override ?? guess }\n}\n\nconst TYPE_AND_MESSAGE = /^([A-Za-z_$][\\w$.]*)(?::\\s?([\\s\\S]*))?$/\n\nfunction splitTypeAndMessage(lines: readonly string[]): StackTraceHeader {\n const first = lines[0] ?? ''\n const match = TYPE_AND_MESSAGE.exec(first)\n const tail = lines.slice(1)\n if (!match) return { message: lines.join('\\n') }\n const message = [match[2] ?? '', ...tail].join('\\n').replace(/^\\n+/, '')\n return { type: match[1], message }\n}\n\n/** Parse a stack trace. Never throws, and every input line appears in the result. */\nexport function parseStackTrace(text: string, options?: ParseStackTraceOptions): ParsedStackTrace {\n const rawLines = text.split('\\n')\n const overflow = rawLines.length > STACK_MAX_LINES ? rawLines.splice(STACK_MAX_LINES) : []\n const rustContext = /panicked at|stack backtrace|RUST_BACKTRACE/.test(text.slice(0, 200_000))\n\n type Item = { type: 'text'; text: string } | { type: 'frame'; draft: Draft }\n const items: Item[] = []\n let python: Draft | undefined\n let rust: Draft | undefined\n\n for (const original of rawLines) {\n const line = original.endsWith('\\r') ? original.slice(0, -1) : original\n const trimmed = line.trim()\n\n if (line.length > LINE_PATTERN_LIMIT || trimmed === '') {\n python = rust = undefined\n items.push({ type: 'text', text: line })\n continue\n }\n\n // Python prints the offending source line, indented, under its frame.\n if (python && /^\\s/.test(line) && !trimmed.startsWith('File \"')) {\n ;(python.detail ??= []).push(line)\n continue\n }\n\n if (rust && rust.file === undefined && RUST_LOCATION.test(trimmed)) {\n const split = splitLocation(RUST_LOCATION.exec(trimmed)![1]!)\n rust.file = split.file\n rust.line = split.line\n rust.column = split.column\n rust.raw = `${rust.raw}\\n${line}`\n continue\n }\n\n const draft = parsePython(trimmed) ?? parseRustFrame(trimmed, rustContext) ?? parseV8(trimmed)\n python = draft?.format === 'python' ? draft : undefined\n rust = draft?.format === 'rust' ? draft : undefined\n if (draft) items.push({ type: 'frame', draft })\n else items.push({ type: 'text', text: line })\n }\n if (overflow.length > 0) items.push({ type: 'text', text: overflow.join('\\n') })\n\n const counts = { v8: 0, python: 0, rust: 0 }\n for (const item of items) if (item.type === 'frame') counts[item.draft.format] += 1\n const total = counts.v8 + counts.python + counts.rust\n const format: StackTraceFormat =\n total === 0\n ? 'unknown'\n : counts.python >= counts.v8 && counts.python >= counts.rust\n ? 'python'\n : counts.rust >= counts.v8\n ? 'rust'\n : 'v8'\n\n const entries: StackEntry[] = items.map((item) =>\n item.type === 'text' ? item : { type: 'frame', frame: finishFrame(item.draft, options) }\n )\n\n if (format === 'unknown') return { format, entries, frames: [] }\n\n const header = liftHeader(entries, format)\n const frames = entries.flatMap((entry) => (entry.type === 'frame' ? [entry.frame] : []))\n return { format, ...(header && { header }), entries, frames }\n}\n\n/**\n * Take the header out of the entries it came from. V8 and Rust print it before\n * the frames; Python prints it after. The lifted lines are removed so they are\n * drawn once, as the header, and nothing else is touched.\n */\nfunction liftHeader(entries: StackEntry[], format: StackTraceFormat): StackTraceHeader | undefined {\n const firstFrame = entries.findIndex((entry) => entry.type === 'frame')\n\n if (format === 'python') {\n let last = entries.length - 1\n while (last >= 0 && (entries[last]!.type !== 'text' || isBlank(entries[last]!))) last -= 1\n let lastFrame = entries.length - 1\n while (lastFrame >= 0 && entries[lastFrame]!.type !== 'frame') lastFrame -= 1\n const entry = entries[last]\n // Only a line shaped like `Type: message` is taken as the header; anything\n // else after the last frame (`[Previous line repeated …]`) stays where it is.\n if (\n !entry ||\n entry.type !== 'text' ||\n last < lastFrame ||\n /^\\s/.test(entry.text) ||\n !TYPE_AND_MESSAGE.test(entry.text.trim())\n ) {\n return undefined\n }\n entries.splice(last, 1)\n return splitTypeAndMessage([entry.text.trim()])\n }\n\n if (format === 'rust') {\n const panicAt = entries.findIndex(\n (entry, index) =>\n index < firstFrame && entry.type === 'text' && /panicked at/.test(entry.text)\n )\n if (panicAt < 0) return undefined\n const first = (entries[panicAt] as { text: string }).text.trim()\n const at = first.indexOf(' at ')\n if (at < 0) return undefined\n const type = first.slice(0, at).trim()\n const after = first.slice(at + 4)\n const old = splitRustOldPanic(after)\n let consumed = 1\n let message: string\n let location: string | undefined\n if (old) {\n message = old.message\n location = old.location\n } else {\n location = after.replace(/:$/, '')\n const body: string[] = []\n for (let i = panicAt + 1; i < firstFrame; i += 1) {\n const entry = entries[i]!\n if (\n entry.type !== 'text' ||\n isBlank(entry) ||\n /^(?:note:|stack backtrace:)/.test(entry.text.trim())\n )\n break\n body.push(entry.text)\n consumed += 1\n }\n message = body.join('\\n')\n }\n entries.splice(panicAt, consumed)\n return { type, message, ...(location && { location }) }\n }\n\n // V8: the block of non-blank lines directly above the first frame. Anything\n // above a blank line — Node prints the source line that threw — stays as text.\n if (firstFrame <= 0) return undefined\n let start = firstFrame\n while (start > 0 && entries[start - 1]!.type === 'text' && !isBlank(entries[start - 1]!))\n start -= 1\n if (start === firstFrame) return undefined\n const lines = entries.slice(start, firstFrame).map((entry) => (entry as { text: string }).text)\n entries.splice(start, firstFrame - start)\n return splitTypeAndMessage(lines)\n}\n\nfunction isBlank(entry: StackEntry): boolean {\n return entry.type === 'text' && entry.text.trim() === ''\n}\n\n// ---------------------------------------------------------------------------\n// Grouping\n// ---------------------------------------------------------------------------\n\nexport type StackBlock =\n | Readonly<{ type: 'frame'; frame: StackFrame }>\n | Readonly<{ type: 'text'; text: string }>\n | Readonly<{ type: 'group'; frames: readonly StackFrame[] }>\n\n/**\n * Fold runs of dependency and runtime frames into groups, so a trace reads as\n * the code the reader owns with the framework's frames one click away.\n *\n * A run of one stays a frame — a disclosure around a single line costs more than\n * it saves. When there is no first-party frame at all nothing is grouped:\n * collapsing everything would hide the entire trace behind a button, which is the\n * opposite of what a reader who owns none of the frames needs.\n */\nexport function groupStackEntries(\n entries: readonly StackEntry[],\n collapse = true\n): readonly StackBlock[] {\n const hasFirstParty = entries.some(\n (entry) => entry.type === 'frame' && entry.frame.kind === 'first-party'\n )\n if (!collapse || !hasFirstParty) return entries\n\n const blocks: StackBlock[] = []\n let run: StackFrame[] = []\n const flush = () => {\n if (run.length >= 2) blocks.push({ type: 'group', frames: run })\n else for (const frame of run) blocks.push({ type: 'frame', frame })\n run = []\n }\n for (const entry of entries) {\n if (entry.type === 'frame' && entry.frame.kind !== 'first-party') {\n run.push(entry.frame)\n continue\n }\n flush()\n blocks.push(entry)\n }\n flush()\n return blocks\n}\n\nconst framesNoun = (count: number, word: string) =>\n `${count.toLocaleString()} ${word} ${count === 1 ? 'frame' : 'frames'}`\n\n/** What a collapsed group says it holds. */\nexport function describeFrameGroup(frames: readonly StackFrame[]): string {\n const count = frames.length\n const dependency = frames.filter((frame) => frame.kind === 'dependency').length\n if (dependency === count) return framesNoun(count, 'dependency')\n if (dependency === 0) return framesNoun(count, 'runtime')\n return `${count.toLocaleString()} dependency and runtime frames`\n}\n\n/** `file:line:col`, with whichever parts the frame has. */\nexport function frameLocation(frame: StackFrame): string | undefined {\n if (frame.file === undefined) return undefined\n const column = frame.column === undefined ? '' : `:${frame.column}`\n const line = frame.line === undefined ? '' : `:${frame.line}`\n return `${frame.file}${line}${line ? column : ''}`\n}\n\nconst LEADING_SPACE = /^[ \\t]*/\n\n/** Remove the indentation every non-blank line shares, so a source snippet sits flush. */\nexport function dedentLines(lines: readonly string[]): string[] {\n let indent = Infinity\n for (const line of lines) {\n if (line.trim() !== '') indent = Math.min(indent, LEADING_SPACE.exec(line)![0].length)\n }\n if (indent === Infinity) return [...lines]\n return lines.map((line) => line.slice(Math.min(indent, LEADING_SPACE.exec(line)![0].length)))\n}\n\nexport type BoundedLines = Readonly<{\n lines: string[]\n /** Lines beyond the bound that are not in `lines` at all. */\n omittedLines: number\n /** Characters beyond the bound, in omitted lines and in the cut end of the last shown line. */\n omittedChars: number\n}>\n\n/**\n * The first lines of `lines`, up to `maxLines` of them and `maxChars` characters\n * between them, with what was left out counted rather than dropped silently. It\n * visits the shown lines and adds up the length of the rest, so the cost does not\n * depend on how much text there is to omit.\n */\nexport function boundLines(\n lines: readonly string[],\n maxLines: number,\n maxChars: number\n): BoundedLines {\n const shown: string[] = []\n let chars = 0\n let index = 0\n let partial = 0\n while (index < lines.length && shown.length < maxLines) {\n const line = lines[index]!\n const room = maxChars - chars\n if (line.length <= room) {\n shown.push(line)\n chars += line.length\n index += 1\n continue\n }\n if (room > 0) {\n shown.push(line.slice(0, room))\n partial = line.length - room\n index += 1\n }\n break\n }\n let omittedChars = partial\n for (let rest = index; rest < lines.length; rest += 1) omittedChars += lines[rest]!.length\n return { lines: shown, omittedLines: lines.length - index, omittedChars }\n}\n"],"mappings":"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"}
@@ -10,9 +10,22 @@ import type { ComponentProps } from 'solid-js';
10
10
  *
11
11
  * The centre track is sized for a lone search field. A bar whose centre holds
12
12
  * more than that, such as a search plus an action beside it, passes
13
- * `centerWidth="wide"`: the centre then stays fluid below 1280px, never
14
- * narrower than 7rem so its controls are not squeezed out by a long leading
15
- * group (which scrolls instead), and takes a wider fixed track above it.
13
+ * `centerWidth="wide"`: the centre then stays fluid below 1280px, with each
14
+ * side group sized to its content but capped between 30% and 40% of the row
15
+ * (a longer group scrolls instead): the cap is 30% on the narrowest rows and
16
+ * grows to 40% as the row widens, so full side groups never squeeze the
17
+ * centre's search out of it and enlarged text never pushes a short leading
18
+ * control such as a navigation toggle out of the row. Above 1280px it takes a
19
+ * wider fixed track.
20
+ *
21
+ * A wide bar also never squeezes its centre to nothing. When the row is
22
+ * narrower than 19rem (measured in rem, so enlarged text reaches it at a wider
23
+ * window) the centre moves to a second row of its own and the two side groups
24
+ * share the first, the trailing group taking the remainder beside the leading
25
+ * group's cap, so the leading and trailing controls and the whole centre each
26
+ * keep a full row instead of fighting for one. The bar grows from one row
27
+ * to two only then. The wide layout places the leading group, centre and
28
+ * trailing group as its first, second and third children.
16
29
  *
17
30
  * On a frameless desktop window this row is also the drag region, so it carries
18
31
  * `window-drag` and the controls inside it opt back out. `macos-inset` and
@@ -31,9 +44,11 @@ export type TopBarProps = ComponentProps<'header'> & {
31
44
  glass?: boolean;
32
45
  /**
33
46
  * How wide the centre track is. `field` (default) suits a lone search field.
34
- * `wide` suits a centre that also holds an action: fluid below 1280px and
35
- * never narrower than 7rem (the side groups scroll to make room), then a
36
- * clamp of 32vw up to 672px between equal side tracks.
47
+ * `wide` suits a centre that also holds an action: fluid below 1280px between
48
+ * side groups sized to their content and capped between 30% and 40% of the
49
+ * row (a longer group scrolls), then a clamp of 32vw up to 672px between equal
50
+ * side tracks.
51
+ * Below a 19rem row the centre drops to a second row of its own.
37
52
  */
38
53
  centerWidth?: 'field' | 'wide';
39
54
  };
@@ -1 +1 @@
1
- {"version":3,"file":"top-bar.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAK9C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IACnD,sDAAsD;IACtD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAC/B,CAAA;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,kCA8BxC;AAYD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;CAAE,kCAqBvF;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,mEAAmE;IACnE,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC3B,kCAgBF;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAY5D;AAED,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IACzD,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kCA8BpD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,kCAqB7E"}
1
+ {"version":3,"file":"top-bar.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAK9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IACnD,sDAAsD;IACtD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,qDAAqD;IACrD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAC/B,CAAA;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,kCAkCxC;AAYD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;CAAE,kCAqBvF;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,mEAAmE;IACnE,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC3B,kCAgBF;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,kCAY5D;AAED,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,QAAQ,CAAC,GAAG;IACzD,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,kCA8BpD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,kCAqB7E"}
@@ -22,7 +22,7 @@ function TopBar(props) {
22
22
  return (() => {
23
23
  var _el$ = _tmpl$();
24
24
  spread(_el$, mergeProps({ get ["class"]() {
25
- return cn("h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]", local.centerWidth === "wide" && "grid-cols-[auto_minmax(7rem,1fr)_auto] gap-2 md:grid-cols-[auto_minmax(7rem,1fr)_auto] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)]", local.glass && "glass-panel", local.draggable && "window-drag", local.macosInset ? "window-inset-macos" : "ps-3", local.windowsInset ? "window-inset-windows" : "pe-3", local.class);
25
+ return cn("h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]", local.centerWidth === "wide" && "@container/top-bar h-auto grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] grid-rows-[minmax(calc(var(--topbar-height)-1px),auto)] gap-x-2 gap-y-0 md:grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)] [&>:nth-child(2)]:@max-[19rem]/top-bar:col-span-full [&>:nth-child(2)]:@max-[19rem]/top-bar:row-start-2 [&>:nth-child(2)]:@max-[19rem]/top-bar:pb-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-span-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-start-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:row-start-1", local.glass && "glass-panel", local.draggable && "window-drag", local.macosInset ? "window-inset-macos" : "ps-3", local.windowsInset ? "window-inset-windows" : "pe-3", local.class);
26
26
  } }, rest), false, false);
27
27
  return _el$;
28
28
  })();
@@ -50,7 +50,7 @@ function TopBarSection(props) {
50
50
  invokeFocusHandler(local.onFocusIn, event);
51
51
  };
52
52
  spread(_el$2, mergeProps({ get ["class"]() {
53
- return cn("window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden", local.align === "end" && "md:justify-end", local.align !== "end" && "justify-start", local.class);
53
+ return cn("window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden", local.align === "end" && "md:justify-end-safe", local.align !== "end" && "justify-start", local.class);
54
54
  } }, rest), false, false);
55
55
  return _el$2;
56
56
  })();
@@ -1 +1 @@
1
- {"version":3,"file":"top-bar.js","names":["ComponentProps","Show","splitProps","Search","cn","TopBarProps","draggable","macosInset","windowsInset","glass","centerWidth","TopBar","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","class","invokeFocusHandler","handler","event","Event","Array","isArray","callback","data","TopBarSection","align","_el$2","_tmpl$2","$$focusin","target","HTMLElement","scrollIntoView","block","inline","onFocusIn","TopBarTitle","_el$3","_tmpl$3","TopBarBreadcrumb","_el$4","_tmpl$4","TopBarSearchProps","placeholder","shortcut","keyshortcuts","TopBarSearch","_el$5","_tmpl$6","_el$6","firstChild","aria-keyshortcuts","_$insert","_$createComponent","when","children","_el$7","_tmpl$5","TopBarPill","label","_el$8","_tmpl$7","_el$9","_$delegateEvents","_$mergeProps","_$createComponent","_$delegateEvents"],"sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { Search } from 'lucide-solid'\nimport { cn } from '#lib/utils'\n\n/**\n * TopBar.\n *\n * The window's title row and primary toolbar. Three tracks: a leading group, a\n * centred search, and a trailing group. On wide screens the search is a fixed\n * function of the window width and the side groups are equal remainders. On\n * narrow screens the middle track yields space to the controls, and each side\n * group scrolls when necessary so keyboard focus can reveal every action.\n *\n * The centre track is sized for a lone search field. A bar whose centre holds\n * more than that, such as a search plus an action beside it, passes\n * `centerWidth=\"wide\"`: the centre then stays fluid below 1280px, never\n * narrower than 7rem so its controls are not squeezed out by a long leading\n * group (which scrolls instead), and takes a wider fixed track above it.\n *\n * On a frameless desktop window this row is also the drag region, so it carries\n * `window-drag` and the controls inside it opt back out. `macos-inset` and\n * `windows-inset` reserve the space the native window controls occupy, which is\n * why the row can look off-centre in a frameless window and be correct: it is\n * centred in the space the user can actually see.\n */\nexport type TopBarProps = ComponentProps<'header'> & {\n /** Make the row draggable, for a frameless window. */\n draggable?: boolean\n /** Reserve space for the macOS traffic lights. */\n macosInset?: boolean\n /** Reserve space for the Windows caption buttons. */\n windowsInset?: boolean\n /** Blur the content scrolling under the bar. */\n glass?: boolean\n /**\n * How wide the centre track is. `field` (default) suits a lone search field.\n * `wide` suits a centre that also holds an action: fluid below 1280px and\n * never narrower than 7rem (the side groups scroll to make room), then a\n * clamp of 32vw up to 672px between equal side tracks.\n */\n centerWidth?: 'field' | 'wide'\n}\n\nexport function TopBar(props: TopBarProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'draggable',\n 'macosInset',\n 'windowsInset',\n 'glass',\n 'centerWidth',\n ])\n\n return (\n <header\n data-slot=\"top-bar\"\n class={cn(\n 'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',\n local.centerWidth === 'wide' &&\n 'grid-cols-[auto_minmax(7rem,1fr)_auto] gap-2 md:grid-cols-[auto_minmax(7rem,1fr)_auto] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)]',\n local.glass && 'glass-panel',\n local.draggable && 'window-drag',\n /* Each edge gets exactly one inline-padding utility. A window inset\n and px-3 would share a layer and tie-break by build order, and a\n build that put px-3 last hid the leading controls under the\n traffic lights. */\n local.macosInset ? 'window-inset-macos' : 'ps-3',\n local.windowsInset ? 'window-inset-windows' : 'pe-3',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nfunction invokeFocusHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\n/** A scrollable side group; equal wide-screen tracks centre the search. */\nexport function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' | 'end' }) {\n const [local, rest] = splitProps(props, ['class', 'align', 'onFocusIn'])\n\n return (\n <div\n data-slot=\"top-bar-section\"\n class={cn(\n 'window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden',\n local.align === 'end' && 'md:justify-end',\n local.align !== 'end' && 'justify-start',\n local.class\n )}\n onFocusIn={(event) => {\n if (event.target instanceof HTMLElement) {\n event.target.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n }\n invokeFocusHandler(local.onFocusIn, event)\n }}\n {...rest}\n />\n )\n}\n\nexport function TopBarTitle(\n props: ComponentProps<'div'> & {\n /** Aligns text within this slot; it does not position the slot. */\n align?: 'start' | 'center'\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'align'])\n return (\n <div\n data-slot=\"top-bar-title\"\n class={cn(\n /* The title slot sits in the drag region; a link, button or field\n placed in it (a breadcrumb, a view's search) must stay pressable. */\n 'truncate text-sm font-medium [&_:is(a,button,input)]:window-no-drag',\n local.align === 'center' && 'text-center',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** A breadcrumb or path readout that yields to the actions when space runs out. */\nexport function TopBarBreadcrumb(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"top-bar-breadcrumb\"\n class={cn(\n 'flex min-w-0 items-center gap-1.5 text-sm [&_:is(a,button,input)]:window-no-drag',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport type TopBarSearchProps = ComponentProps<'button'> & {\n /** The hint shown inside the field, e.g. \"Search projects and files\". */\n placeholder?: string\n /** The shortcut chip drawn at the trailing edge, e.g. \"⌘K\". */\n shortcut?: string\n /**\n * The parseable chord behind `shortcut`, e.g. `Meta+K`. The chip is\n * `aria-hidden` — reading it out would pollute the button's accessible\n * name — so this is what assistive technology announces instead. Supply it\n * whenever `shortcut` is drawn.\n */\n keyshortcuts?: string\n}\n\n/**\n * The search affordance in the middle track.\n *\n * It is a button, not an input, and that is deliberate: clicking it opens the\n * command palette, which already owns the input, the ranking and the keyboard\n * handling. A second live input here would be a second source of truth for the\n * same query. It is styled to read as a field because that is what it behaves\n * like from the user's side.\n */\nexport function TopBarSearch(props: TopBarSearchProps) {\n const [local, rest] = splitProps(props, ['class', 'placeholder', 'shortcut', 'keyshortcuts'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-search\"\n class={cn(\n 'text-muted-foreground flex h-control-md w-full min-w-(--control-height-md) items-center justify-center gap-2 rounded-md border border-input px-3 text-sm sm:justify-start',\n 'transition-colors ease-out outline-none',\n 'hover:bg-surface-hover hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag',\n local.class\n )}\n aria-keyshortcuts={local.keyshortcuts}\n {...rest}\n >\n <Search aria-hidden=\"true\" class=\"size-4 shrink-0 sm:hidden\" />\n <span class=\"sr-only sm:not-sr-only sm:truncate\">{local.placeholder ?? 'Search'}</span>\n <Show when={local.shortcut}>\n <kbd\n aria-hidden=\"true\"\n class=\"bg-muted ms-auto hidden h-5 shrink-0 items-center rounded-sm border border-border px-1.5 font-code text-code sm:flex\"\n >\n {local.shortcut}\n </kbd>\n </Show>\n </button>\n )\n}\n\n/**\n * A labelled top-bar control — the readouts KiroCrew puts in the trailing\n * group. Container queries on the group decide which of these survive a narrow\n * window; a caller marks the cheap-to-drop ones and the group handles the rest.\n */\nexport function TopBarPill(props: ComponentProps<'button'> & { label: string }) {\n const [local, rest] = splitProps(props, ['class', 'label', 'children'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-pill\"\n class={cn(\n 'text-muted-foreground inline-flex h-control-sm shrink-0 items-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium',\n 'transition-colors ease-out outline-none',\n 'hover:border-input hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag [&_svg]:size-3.5 [&_svg]:shrink-0',\n local.class\n )}\n {...rest}\n >\n {local.children}\n <span>{local.label}</span>\n </button>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"top-bar.js","names":["ComponentProps","Show","splitProps","Search","cn","TopBarProps","draggable","macosInset","windowsInset","glass","centerWidth","TopBar","props","local","rest","_el$","_tmpl$","_$spread","_$mergeProps","class","invokeFocusHandler","handler","event","Event","Array","isArray","callback","data","TopBarSection","align","_el$2","_tmpl$2","$$focusin","target","HTMLElement","scrollIntoView","block","inline","onFocusIn","TopBarTitle","_el$3","_tmpl$3","TopBarBreadcrumb","_el$4","_tmpl$4","TopBarSearchProps","placeholder","shortcut","keyshortcuts","TopBarSearch","_el$5","_tmpl$6","_el$6","firstChild","aria-keyshortcuts","_$insert","_$createComponent","when","children","_el$7","_tmpl$5","TopBarPill","label","_el$8","_tmpl$7","_el$9","_$delegateEvents","_$mergeProps","_$createComponent","_$delegateEvents"],"sources":["../../../../src/components/layout/top-bar/top-bar.tsx"],"sourcesContent":["import type { ComponentProps } from 'solid-js'\nimport { Show, splitProps } from 'solid-js'\nimport { Search } from 'lucide-solid'\nimport { cn } from '#lib/utils'\n\n/**\n * TopBar.\n *\n * The window's title row and primary toolbar. Three tracks: a leading group, a\n * centred search, and a trailing group. On wide screens the search is a fixed\n * function of the window width and the side groups are equal remainders. On\n * narrow screens the middle track yields space to the controls, and each side\n * group scrolls when necessary so keyboard focus can reveal every action.\n *\n * The centre track is sized for a lone search field. A bar whose centre holds\n * more than that, such as a search plus an action beside it, passes\n * `centerWidth=\"wide\"`: the centre then stays fluid below 1280px, with each\n * side group sized to its content but capped between 30% and 40% of the row\n * (a longer group scrolls instead): the cap is 30% on the narrowest rows and\n * grows to 40% as the row widens, so full side groups never squeeze the\n * centre's search out of it and enlarged text never pushes a short leading\n * control such as a navigation toggle out of the row. Above 1280px it takes a\n * wider fixed track.\n *\n * A wide bar also never squeezes its centre to nothing. When the row is\n * narrower than 19rem (measured in rem, so enlarged text reaches it at a wider\n * window) the centre moves to a second row of its own and the two side groups\n * share the first, the trailing group taking the remainder beside the leading\n * group's cap, so the leading and trailing controls and the whole centre each\n * keep a full row instead of fighting for one. The bar grows from one row\n * to two only then. The wide layout places the leading group, centre and\n * trailing group as its first, second and third children.\n *\n * On a frameless desktop window this row is also the drag region, so it carries\n * `window-drag` and the controls inside it opt back out. `macos-inset` and\n * `windows-inset` reserve the space the native window controls occupy, which is\n * why the row can look off-centre in a frameless window and be correct: it is\n * centred in the space the user can actually see.\n */\nexport type TopBarProps = ComponentProps<'header'> & {\n /** Make the row draggable, for a frameless window. */\n draggable?: boolean\n /** Reserve space for the macOS traffic lights. */\n macosInset?: boolean\n /** Reserve space for the Windows caption buttons. */\n windowsInset?: boolean\n /** Blur the content scrolling under the bar. */\n glass?: boolean\n /**\n * How wide the centre track is. `field` (default) suits a lone search field.\n * `wide` suits a centre that also holds an action: fluid below 1280px between\n * side groups sized to their content and capped between 30% and 40% of the\n * row (a longer group scrolls), then a clamp of 32vw up to 672px between equal\n * side tracks.\n * Below a 19rem row the centre drops to a second row of its own.\n */\n centerWidth?: 'field' | 'wide'\n}\n\nexport function TopBar(props: TopBarProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'draggable',\n 'macosInset',\n 'windowsInset',\n 'glass',\n 'centerWidth',\n ])\n\n return (\n <header\n data-slot=\"top-bar\"\n class={cn(\n 'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',\n /* The bar is its own container so its children can move to a second\n row by the row's width in rem, which enlarged text shrinks. The\n first row keeps the bar's height (less the 1px bottom border); the\n second only exists when the centre has dropped onto it. */\n local.centerWidth === 'wide' &&\n '@container/top-bar h-auto grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] grid-rows-[minmax(calc(var(--topbar-height)-1px),auto)] gap-x-2 gap-y-0 md:grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)] [&>:nth-child(2)]:@max-[19rem]/top-bar:col-span-full [&>:nth-child(2)]:@max-[19rem]/top-bar:row-start-2 [&>:nth-child(2)]:@max-[19rem]/top-bar:pb-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-span-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-start-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:row-start-1',\n local.glass && 'glass-panel',\n local.draggable && 'window-drag',\n /* Each edge gets exactly one inline-padding utility. A window inset\n and px-3 would share a layer and tie-break by build order, and a\n build that put px-3 last hid the leading controls under the\n traffic lights. */\n local.macosInset ? 'window-inset-macos' : 'ps-3',\n local.windowsInset ? 'window-inset-windows' : 'pe-3',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nfunction invokeFocusHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n const callback = handler as (event: Event) => void\n callback(event)\n }\n}\n\n/** A scrollable side group; equal wide-screen tracks centre the search. */\nexport function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' | 'end' }) {\n const [local, rest] = splitProps(props, ['class', 'align', 'onFocusIn'])\n\n return (\n <div\n data-slot=\"top-bar-section\"\n class={cn(\n 'window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden',\n local.align === 'end' && 'md:justify-end-safe',\n local.align !== 'end' && 'justify-start',\n local.class\n )}\n onFocusIn={(event) => {\n if (event.target instanceof HTMLElement) {\n event.target.scrollIntoView({ block: 'nearest', inline: 'nearest' })\n }\n invokeFocusHandler(local.onFocusIn, event)\n }}\n {...rest}\n />\n )\n}\n\nexport function TopBarTitle(\n props: ComponentProps<'div'> & {\n /** Aligns text within this slot; it does not position the slot. */\n align?: 'start' | 'center'\n }\n) {\n const [local, rest] = splitProps(props, ['class', 'align'])\n return (\n <div\n data-slot=\"top-bar-title\"\n class={cn(\n /* The title slot sits in the drag region; a link, button or field\n placed in it (a breadcrumb, a view's search) must stay pressable. */\n 'truncate text-sm font-medium [&_:is(a,button,input)]:window-no-drag',\n local.align === 'center' && 'text-center',\n local.class\n )}\n {...rest}\n />\n )\n}\n\n/** A breadcrumb or path readout that yields to the actions when space runs out. */\nexport function TopBarBreadcrumb(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"top-bar-breadcrumb\"\n class={cn(\n 'flex min-w-0 items-center gap-1.5 text-sm [&_:is(a,button,input)]:window-no-drag',\n local.class\n )}\n {...rest}\n />\n )\n}\n\nexport type TopBarSearchProps = ComponentProps<'button'> & {\n /** The hint shown inside the field, e.g. \"Search projects and files\". */\n placeholder?: string\n /** The shortcut chip drawn at the trailing edge, e.g. \"⌘K\". */\n shortcut?: string\n /**\n * The parseable chord behind `shortcut`, e.g. `Meta+K`. The chip is\n * `aria-hidden` — reading it out would pollute the button's accessible\n * name — so this is what assistive technology announces instead. Supply it\n * whenever `shortcut` is drawn.\n */\n keyshortcuts?: string\n}\n\n/**\n * The search affordance in the middle track.\n *\n * It is a button, not an input, and that is deliberate: clicking it opens the\n * command palette, which already owns the input, the ranking and the keyboard\n * handling. A second live input here would be a second source of truth for the\n * same query. It is styled to read as a field because that is what it behaves\n * like from the user's side.\n */\nexport function TopBarSearch(props: TopBarSearchProps) {\n const [local, rest] = splitProps(props, ['class', 'placeholder', 'shortcut', 'keyshortcuts'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-search\"\n class={cn(\n 'text-muted-foreground flex h-control-md w-full min-w-(--control-height-md) items-center justify-center gap-2 rounded-md border border-input px-3 text-sm sm:justify-start',\n 'transition-colors ease-out outline-none',\n 'hover:bg-surface-hover hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag',\n local.class\n )}\n aria-keyshortcuts={local.keyshortcuts}\n {...rest}\n >\n <Search aria-hidden=\"true\" class=\"size-4 shrink-0 sm:hidden\" />\n <span class=\"sr-only sm:not-sr-only sm:truncate\">{local.placeholder ?? 'Search'}</span>\n <Show when={local.shortcut}>\n <kbd\n aria-hidden=\"true\"\n class=\"bg-muted ms-auto hidden h-5 shrink-0 items-center rounded-sm border border-border px-1.5 font-code text-code sm:flex\"\n >\n {local.shortcut}\n </kbd>\n </Show>\n </button>\n )\n}\n\n/**\n * A labelled top-bar control — the readouts KiroCrew puts in the trailing\n * group. Container queries on the group decide which of these survive a narrow\n * window; a caller marks the cheap-to-drop ones and the group handles the rest.\n */\nexport function TopBarPill(props: ComponentProps<'button'> & { label: string }) {\n const [local, rest] = splitProps(props, ['class', 'label', 'children'])\n\n return (\n <button\n type=\"button\"\n data-slot=\"top-bar-pill\"\n class={cn(\n 'text-muted-foreground inline-flex h-control-sm shrink-0 items-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium',\n 'transition-colors ease-out outline-none',\n 'hover:border-input hover:text-foreground',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n 'window-no-drag [&_svg]:size-3.5 [&_svg]:shrink-0',\n local.class\n )}\n {...rest}\n >\n {local.children}\n <span>{local.label}</span>\n </button>\n )\n}\n"],"mappings":";;;;;;;;;;;;AA2DA,SAAgBW,OAAOC,OAAoB;CACzC,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;CAAa,CACd;CAED,cAAA;EAAA,IAAAG,OAAAC,OAAA;EAAAC,OAAAF,MAAAG,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWd,GACL,yOAKAS,MAAMH,gBAAgB,UACpB,+sBACFG,MAAMJ,SAAS,eACfI,MAAMP,aAAa,eAKnBO,MAAMN,aAAa,uBAAuB,QAC1CM,MAAML,eAAe,yBAAyB,QAC9CK,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAC;CAAA,EAAA,CAAA;AAGd;AAEA,SAASK,mBAAmBC,SAAkBC,OAAc;CAC1D,IAAIE,MAAMC,QAAQJ,OAAO,GAAG;EAC1B,MAAM,CAACK,UAAUC,QAAQN;EACzBK,SAASC,MAAML,KAAK;CACtB,OAAO,IAAI,OAAOD,YAAY,YAE5BK,QAASJ,KAAK;AAElB;;AAGA,SAAgBM,cAAchB,OAA4D;CACxF,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EAAC;EAAS;EAAS;CAAW,CAAC;CAEvE,cAAA;EAAA,IAAAkB,QAAAC,QAAA;EAAAD,MAAAE,aASgBV,UAAU;GACpB,IAAIA,MAAMW,kBAAkBC,aAC1BZ,MAAMW,OAAOE,eAAe;IAAEC,OAAO;IAAWC,QAAQ;GAAU,CAAC;GAErEjB,mBAAmBP,MAAMyB,WAAWhB,KAAK;EAC3C;EAACL,OAAAa,OAAAZ,WAAA,EAAA,KAAA,WAAA;GAAA,OAXMd,GACL,oFACAS,MAAMgB,UAAU,SAAS,uBACzBhB,MAAMgB,UAAU,SAAS,iBACzBhB,MAAMM,KACR;EAAC,EAAA,GAOGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAAgB;CAAA,EAAA,CAAA;AAGd;AAEA,SAAgBS,YACd3B,OAIA;CACA,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO,CAAC,SAAS,OAAO,CAAC;CAC1D,cAAA;EAAA,IAAA4B,QAAAC,QAAA;EAAAxB,OAAAuB,OAAAtB,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWd,GAGL,uEACAS,MAAMgB,UAAU,YAAY,eAC5BhB,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAA0B;CAAA,EAAA,CAAA;AAGd;;AAGA,SAAgBE,iBAAiB9B,OAA8B;CAC7D,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO,CAAC,OAAO,CAAC;CACjD,cAAA;EAAA,IAAA+B,QAAAC,QAAA;EAAA3B,OAAA0B,OAAAzB,WAAA,EAAA,KAAA,WAAA;GAAA,OAGWd,GACL,oFACAS,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,KAAA;EAAA,OAAA6B;CAAA,EAAA,CAAA;AAGd;;;;;;;;;;AAyBA,SAAgBM,aAAarC,OAA0B;CACrD,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EAAC;EAAS;EAAe;EAAY;CAAc,CAAC;CAE5F,cAAA;EAAA,IAAAsC,QAAAC,QAAA,GAAAC,QAAAF,MAAAG;EAAApC,OAAAiC,OAAAhC,WAAA;GAAA,KAAA,WAAA;IAAA,OAIWd,GACL,6KACA,2CACA,gDACA,oFACA,kBACAS,MAAMM,KACR;GAAC;GAAA,KAAA,uBAAA;IAAA,OACkBN,MAAMmC;GAAY;EAAA,GACjClC,IAAI,GAAA,OAAA,IAAA;EAAAyC,OAAAL,OAAAM,gBAEPrD,QAAM;GAAA,eAAA;GAAA,SAAA;EAAA,CAAA,GAAAiD,KAAA;EAAAG,OAAAH,aAC2CvC,MAAMiC,eAAe,QAAQ;EAAAS,OAAAL,OAAAM,gBAC9EvD,MAAI;GAAA,IAACwD,OAAI;IAAA,OAAE5C,MAAMkC;GAAQ;GAAA,IAAAW,WAAA;IAAA,IAAAC,QAAAC,QAAA;IAAAL,OAAAI,aAKrB9C,MAAMkC,QAAQ;IAAA,OAAAY;GAAA;EAAA,CAAA,GAAA,IAAA;EAAA,OAAAT;CAAA,EAAA,CAAA;AAKzB;;;;;;AAOA,SAAgBW,WAAWjD,OAAqD;CAC9E,MAAM,CAACC,OAAOC,QAAQZ,WAAWU,OAAO;EAAC;EAAS;EAAS;CAAU,CAAC;CAEtE,cAAA;EAAA,IAAAmD,QAAAC,QAAA,GAAAC,QAAAF,MAAAV;EAAApC,OAAA8C,OAAA7C,WAAA,EAAA,KAAA,WAAA;GAAA,OAIWd,GACL,2IACA,2CACA,4CACA,oFACA,oDACAS,MAAMM,KACR;EAAC,EAAA,GACGL,IAAI,GAAA,OAAA,IAAA;EAAAyC,OAAAQ,aAEPlD,MAAM6C,UAAQO,KAAA;EAAAV,OAAAU,aACRpD,MAAMiD,KAAK;EAAA,OAAAC;CAAA,EAAA,CAAA;AAGxB;AAACG,eAAA,CAAA,SAAA,CAAA"}
@@ -100,6 +100,31 @@ export function splitLocation(location: string): { file: string; line?: number;
100
100
  return { file: last.rest, line: last.value }
101
101
  }
102
102
 
103
+ /**
104
+ * Split the pre-1.78 Rust panic suffix `'message', location`. The message may
105
+ * contain `', ` itself, and the greedy pattern this replaces took the last
106
+ * occurrence with a non-space behind it — this scan keeps that last-wins
107
+ * answer, in one pass, so a message repeating the marker stays linear.
108
+ */
109
+ function splitRustOldPanic(after: string): { message: string; location: string } | undefined {
110
+ // As in `parsePython`: no `.` in the old pattern could cross a line break.
111
+ if (/[\n\r\u2028\u2029]/.test(after) || !after.startsWith("'")) return undefined
112
+ let close: number | undefined
113
+ let search = 1
114
+ for (;;) {
115
+ const at = after.indexOf("', ", search)
116
+ if (at < 0) break
117
+ search = at + 1
118
+ const start = at + 3
119
+ if (start >= after.length) continue
120
+ // Preserve ECMAScript \S, including NBSP/BOM and Unicode space separators.
121
+ if (/\s/.test(after[start]!)) continue
122
+ close = at
123
+ }
124
+ if (close === undefined) return undefined
125
+ return { message: after.slice(1, close), location: after.slice(close + 3) }
126
+ }
127
+
103
128
  const FILE_LIKE = /[\\/]|\.[A-Za-z0-9]{1,6}$/
104
129
  const URL_LIKE = /^node:/i
105
130
 
@@ -154,13 +179,58 @@ function parseV8(trimmed: string): Draft | undefined {
154
179
  return draft
155
180
  }
156
181
 
157
- const PYTHON_FRAME = /^File "(.*)", line (\d+)(?:, in (.*))?$/
182
+ const PYTHON_PREFIX = 'File "'
183
+ const PYTHON_LINE = '", line '
184
+ const PYTHON_IN = ', in '
185
+
186
+ /** Whether a character is an ASCII digit, as `\d` was in the pattern this replaced. */
187
+ function isDigit(code: number): boolean {
188
+ return code >= 48 && code <= 57
189
+ }
158
190
 
191
+ /**
192
+ * Read a Python frame: `File "<file>", line <n>[, in <fn>]`.
193
+ *
194
+ * Scanned, not matched. A line repeating the frame shape many times must still
195
+ * cost one pass, so the closing `", line ` is found by walking its candidates
196
+ * left to right. The last valid candidate wins — what the greedy pattern this
197
+ * replaces would have settled on — so a file path containing a quote still
198
+ * parses exactly as it always did.
199
+ */
159
200
  function parsePython(trimmed: string): Draft | undefined {
160
- const match = PYTHON_FRAME.exec(trimmed)
161
- if (!match) return undefined
162
- const draft: Draft = { raw: trimmed, format: 'python', file: match[1], line: Number(match[2]) }
163
- if (match[3]) draft.function = match[3]
201
+ // `.` never matches a line break, so the whole-line pattern this replaces
202
+ // could not have matched a string containing one either.
203
+ if (/[\n\r\u2028\u2029]/.test(trimmed) || !trimmed.startsWith(PYTHON_PREFIX)) return undefined
204
+ let close: number | undefined
205
+ let digits = 0
206
+ let rest = 0
207
+ let hasIn = false
208
+ let search = PYTHON_PREFIX.length
209
+ for (;;) {
210
+ const at = trimmed.indexOf(PYTHON_LINE, search)
211
+ if (at < 0) break
212
+ search = at + 1
213
+ let end = at + PYTHON_LINE.length
214
+ const start = end
215
+ while (end < trimmed.length && isDigit(trimmed.charCodeAt(end))) end += 1
216
+ if (end === start) continue
217
+ if (end < trimmed.length && !trimmed.startsWith(PYTHON_IN, end)) continue
218
+ close = at
219
+ digits = end - start
220
+ rest = end
221
+ hasIn = end < trimmed.length
222
+ }
223
+ if (close === undefined) return undefined
224
+ const draft: Draft = {
225
+ raw: trimmed,
226
+ format: 'python',
227
+ file: trimmed.slice(PYTHON_PREFIX.length, close),
228
+ line: Number(trimmed.slice(rest - digits, rest)),
229
+ }
230
+ if (hasIn) {
231
+ const fn = trimmed.slice(rest + PYTHON_IN.length)
232
+ if (fn) draft.function = fn
233
+ }
164
234
  return draft
165
235
  }
166
236
 
@@ -341,13 +411,13 @@ function liftHeader(entries: StackEntry[], format: StackTraceFormat): StackTrace
341
411
  if (at < 0) return undefined
342
412
  const type = first.slice(0, at).trim()
343
413
  const after = first.slice(at + 4)
344
- const old = /^'(.*)', (\S.*)$/.exec(after)
414
+ const old = splitRustOldPanic(after)
345
415
  let consumed = 1
346
416
  let message: string
347
417
  let location: string | undefined
348
418
  if (old) {
349
- message = old[1]!
350
- location = old[2]
419
+ message = old.message
420
+ location = old.location
351
421
  } else {
352
422
  location = after.replace(/:$/, '')
353
423
  const body: string[] = []
@@ -14,9 +14,22 @@ import { cn } from '../../../lib/utils'
14
14
  *
15
15
  * The centre track is sized for a lone search field. A bar whose centre holds
16
16
  * more than that, such as a search plus an action beside it, passes
17
- * `centerWidth="wide"`: the centre then stays fluid below 1280px, never
18
- * narrower than 7rem so its controls are not squeezed out by a long leading
19
- * group (which scrolls instead), and takes a wider fixed track above it.
17
+ * `centerWidth="wide"`: the centre then stays fluid below 1280px, with each
18
+ * side group sized to its content but capped between 30% and 40% of the row
19
+ * (a longer group scrolls instead): the cap is 30% on the narrowest rows and
20
+ * grows to 40% as the row widens, so full side groups never squeeze the
21
+ * centre's search out of it and enlarged text never pushes a short leading
22
+ * control such as a navigation toggle out of the row. Above 1280px it takes a
23
+ * wider fixed track.
24
+ *
25
+ * A wide bar also never squeezes its centre to nothing. When the row is
26
+ * narrower than 19rem (measured in rem, so enlarged text reaches it at a wider
27
+ * window) the centre moves to a second row of its own and the two side groups
28
+ * share the first, the trailing group taking the remainder beside the leading
29
+ * group's cap, so the leading and trailing controls and the whole centre each
30
+ * keep a full row instead of fighting for one. The bar grows from one row
31
+ * to two only then. The wide layout places the leading group, centre and
32
+ * trailing group as its first, second and third children.
20
33
  *
21
34
  * On a frameless desktop window this row is also the drag region, so it carries
22
35
  * `window-drag` and the controls inside it opt back out. `macos-inset` and
@@ -35,9 +48,11 @@ export type TopBarProps = ComponentProps<'header'> & {
35
48
  glass?: boolean
36
49
  /**
37
50
  * How wide the centre track is. `field` (default) suits a lone search field.
38
- * `wide` suits a centre that also holds an action: fluid below 1280px and
39
- * never narrower than 7rem (the side groups scroll to make room), then a
40
- * clamp of 32vw up to 672px between equal side tracks.
51
+ * `wide` suits a centre that also holds an action: fluid below 1280px between
52
+ * side groups sized to their content and capped between 30% and 40% of the
53
+ * row (a longer group scrolls), then a clamp of 32vw up to 672px between equal
54
+ * side tracks.
55
+ * Below a 19rem row the centre drops to a second row of its own.
41
56
  */
42
57
  centerWidth?: 'field' | 'wide'
43
58
  }
@@ -57,8 +72,12 @@ export function TopBar(props: TopBarProps) {
57
72
  data-slot="top-bar"
58
73
  class={cn(
59
74
  'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',
75
+ /* The bar is its own container so its children can move to a second
76
+ row by the row's width in rem, which enlarged text shrinks. The
77
+ first row keeps the bar's height (less the 1px bottom border); the
78
+ second only exists when the centre has dropped onto it. */
60
79
  local.centerWidth === 'wide' &&
61
- 'grid-cols-[auto_minmax(7rem,1fr)_auto] gap-2 md:grid-cols-[auto_minmax(7rem,1fr)_auto] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)]',
80
+ '@container/top-bar h-auto grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] grid-rows-[minmax(calc(var(--topbar-height)-1px),auto)] gap-x-2 gap-y-0 md:grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)] [&>:nth-child(2)]:@max-[19rem]/top-bar:col-span-full [&>:nth-child(2)]:@max-[19rem]/top-bar:row-start-2 [&>:nth-child(2)]:@max-[19rem]/top-bar:pb-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-span-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-start-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:row-start-1',
62
81
  local.glass && 'glass-panel',
63
82
  local.draggable && 'window-drag',
64
83
  /* Each edge gets exactly one inline-padding utility. A window inset
@@ -93,7 +112,7 @@ export function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' |
93
112
  data-slot="top-bar-section"
94
113
  class={cn(
95
114
  'window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden',
96
- local.align === 'end' && 'md:justify-end',
115
+ local.align === 'end' && 'md:justify-end-safe',
97
116
  local.align !== 'end' && 'justify-start',
98
117
  local.class
99
118
  )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.142.2",
3
+ "version": "0.142.4",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -100,6 +100,31 @@ export function splitLocation(location: string): { file: string; line?: number;
100
100
  return { file: last.rest, line: last.value }
101
101
  }
102
102
 
103
+ /**
104
+ * Split the pre-1.78 Rust panic suffix `'message', location`. The message may
105
+ * contain `', ` itself, and the greedy pattern this replaces took the last
106
+ * occurrence with a non-space behind it — this scan keeps that last-wins
107
+ * answer, in one pass, so a message repeating the marker stays linear.
108
+ */
109
+ function splitRustOldPanic(after: string): { message: string; location: string } | undefined {
110
+ // As in `parsePython`: no `.` in the old pattern could cross a line break.
111
+ if (/[\n\r\u2028\u2029]/.test(after) || !after.startsWith("'")) return undefined
112
+ let close: number | undefined
113
+ let search = 1
114
+ for (;;) {
115
+ const at = after.indexOf("', ", search)
116
+ if (at < 0) break
117
+ search = at + 1
118
+ const start = at + 3
119
+ if (start >= after.length) continue
120
+ // Preserve ECMAScript \S, including NBSP/BOM and Unicode space separators.
121
+ if (/\s/.test(after[start]!)) continue
122
+ close = at
123
+ }
124
+ if (close === undefined) return undefined
125
+ return { message: after.slice(1, close), location: after.slice(close + 3) }
126
+ }
127
+
103
128
  const FILE_LIKE = /[\\/]|\.[A-Za-z0-9]{1,6}$/
104
129
  const URL_LIKE = /^node:/i
105
130
 
@@ -154,13 +179,58 @@ function parseV8(trimmed: string): Draft | undefined {
154
179
  return draft
155
180
  }
156
181
 
157
- const PYTHON_FRAME = /^File "(.*)", line (\d+)(?:, in (.*))?$/
182
+ const PYTHON_PREFIX = 'File "'
183
+ const PYTHON_LINE = '", line '
184
+ const PYTHON_IN = ', in '
185
+
186
+ /** Whether a character is an ASCII digit, as `\d` was in the pattern this replaced. */
187
+ function isDigit(code: number): boolean {
188
+ return code >= 48 && code <= 57
189
+ }
158
190
 
191
+ /**
192
+ * Read a Python frame: `File "<file>", line <n>[, in <fn>]`.
193
+ *
194
+ * Scanned, not matched. A line repeating the frame shape many times must still
195
+ * cost one pass, so the closing `", line ` is found by walking its candidates
196
+ * left to right. The last valid candidate wins — what the greedy pattern this
197
+ * replaces would have settled on — so a file path containing a quote still
198
+ * parses exactly as it always did.
199
+ */
159
200
  function parsePython(trimmed: string): Draft | undefined {
160
- const match = PYTHON_FRAME.exec(trimmed)
161
- if (!match) return undefined
162
- const draft: Draft = { raw: trimmed, format: 'python', file: match[1], line: Number(match[2]) }
163
- if (match[3]) draft.function = match[3]
201
+ // `.` never matches a line break, so the whole-line pattern this replaces
202
+ // could not have matched a string containing one either.
203
+ if (/[\n\r\u2028\u2029]/.test(trimmed) || !trimmed.startsWith(PYTHON_PREFIX)) return undefined
204
+ let close: number | undefined
205
+ let digits = 0
206
+ let rest = 0
207
+ let hasIn = false
208
+ let search = PYTHON_PREFIX.length
209
+ for (;;) {
210
+ const at = trimmed.indexOf(PYTHON_LINE, search)
211
+ if (at < 0) break
212
+ search = at + 1
213
+ let end = at + PYTHON_LINE.length
214
+ const start = end
215
+ while (end < trimmed.length && isDigit(trimmed.charCodeAt(end))) end += 1
216
+ if (end === start) continue
217
+ if (end < trimmed.length && !trimmed.startsWith(PYTHON_IN, end)) continue
218
+ close = at
219
+ digits = end - start
220
+ rest = end
221
+ hasIn = end < trimmed.length
222
+ }
223
+ if (close === undefined) return undefined
224
+ const draft: Draft = {
225
+ raw: trimmed,
226
+ format: 'python',
227
+ file: trimmed.slice(PYTHON_PREFIX.length, close),
228
+ line: Number(trimmed.slice(rest - digits, rest)),
229
+ }
230
+ if (hasIn) {
231
+ const fn = trimmed.slice(rest + PYTHON_IN.length)
232
+ if (fn) draft.function = fn
233
+ }
164
234
  return draft
165
235
  }
166
236
 
@@ -341,13 +411,13 @@ function liftHeader(entries: StackEntry[], format: StackTraceFormat): StackTrace
341
411
  if (at < 0) return undefined
342
412
  const type = first.slice(0, at).trim()
343
413
  const after = first.slice(at + 4)
344
- const old = /^'(.*)', (\S.*)$/.exec(after)
414
+ const old = splitRustOldPanic(after)
345
415
  let consumed = 1
346
416
  let message: string
347
417
  let location: string | undefined
348
418
  if (old) {
349
- message = old[1]!
350
- location = old[2]
419
+ message = old.message
420
+ location = old.location
351
421
  } else {
352
422
  location = after.replace(/:$/, '')
353
423
  const body: string[] = []
@@ -14,9 +14,22 @@ import { cn } from '#lib/utils'
14
14
  *
15
15
  * The centre track is sized for a lone search field. A bar whose centre holds
16
16
  * more than that, such as a search plus an action beside it, passes
17
- * `centerWidth="wide"`: the centre then stays fluid below 1280px, never
18
- * narrower than 7rem so its controls are not squeezed out by a long leading
19
- * group (which scrolls instead), and takes a wider fixed track above it.
17
+ * `centerWidth="wide"`: the centre then stays fluid below 1280px, with each
18
+ * side group sized to its content but capped between 30% and 40% of the row
19
+ * (a longer group scrolls instead): the cap is 30% on the narrowest rows and
20
+ * grows to 40% as the row widens, so full side groups never squeeze the
21
+ * centre's search out of it and enlarged text never pushes a short leading
22
+ * control such as a navigation toggle out of the row. Above 1280px it takes a
23
+ * wider fixed track.
24
+ *
25
+ * A wide bar also never squeezes its centre to nothing. When the row is
26
+ * narrower than 19rem (measured in rem, so enlarged text reaches it at a wider
27
+ * window) the centre moves to a second row of its own and the two side groups
28
+ * share the first, the trailing group taking the remainder beside the leading
29
+ * group's cap, so the leading and trailing controls and the whole centre each
30
+ * keep a full row instead of fighting for one. The bar grows from one row
31
+ * to two only then. The wide layout places the leading group, centre and
32
+ * trailing group as its first, second and third children.
20
33
  *
21
34
  * On a frameless desktop window this row is also the drag region, so it carries
22
35
  * `window-drag` and the controls inside it opt back out. `macos-inset` and
@@ -35,9 +48,11 @@ export type TopBarProps = ComponentProps<'header'> & {
35
48
  glass?: boolean
36
49
  /**
37
50
  * How wide the centre track is. `field` (default) suits a lone search field.
38
- * `wide` suits a centre that also holds an action: fluid below 1280px and
39
- * never narrower than 7rem (the side groups scroll to make room), then a
40
- * clamp of 32vw up to 672px between equal side tracks.
51
+ * `wide` suits a centre that also holds an action: fluid below 1280px between
52
+ * side groups sized to their content and capped between 30% and 40% of the
53
+ * row (a longer group scrolls), then a clamp of 32vw up to 672px between equal
54
+ * side tracks.
55
+ * Below a 19rem row the centre drops to a second row of its own.
41
56
  */
42
57
  centerWidth?: 'field' | 'wide'
43
58
  }
@@ -57,8 +72,12 @@ export function TopBar(props: TopBarProps) {
57
72
  data-slot="top-bar"
58
73
  class={cn(
59
74
  'h-topbar relative z-(--z-sticky) grid shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 border-b border-border bg-card text-card-foreground md:grid-cols-[minmax(0,1fr)_clamp(240px,22vw,480px)_minmax(0,1fr)]',
75
+ /* The bar is its own container so its children can move to a second
76
+ row by the row's width in rem, which enlarged text shrinks. The
77
+ first row keeps the bar's height (less the 1px bottom border); the
78
+ second only exists when the centre has dropped onto it. */
60
79
  local.centerWidth === 'wide' &&
61
- 'grid-cols-[auto_minmax(7rem,1fr)_auto] gap-2 md:grid-cols-[auto_minmax(7rem,1fr)_auto] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)]',
80
+ '@container/top-bar h-auto grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] grid-rows-[minmax(calc(var(--topbar-height)-1px),auto)] gap-x-2 gap-y-0 md:grid-cols-[fit-content(max(30%,min(40%,calc(50%-3.75rem))))_minmax(0,1fr)_fit-content(max(30%,min(40%,calc(50%-3.75rem))))] xl:grid-cols-[minmax(0,1fr)_clamp(240px,32vw,672px)_minmax(0,1fr)] [&>:nth-child(2)]:@max-[19rem]/top-bar:col-span-full [&>:nth-child(2)]:@max-[19rem]/top-bar:row-start-2 [&>:nth-child(2)]:@max-[19rem]/top-bar:pb-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-span-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:col-start-2 [&>:nth-child(3)]:@max-[19rem]/top-bar:row-start-1',
62
81
  local.glass && 'glass-panel',
63
82
  local.draggable && 'window-drag',
64
83
  /* Each edge gets exactly one inline-padding utility. A window inset
@@ -93,7 +112,7 @@ export function TopBarSection(props: ComponentProps<'div'> & { align?: 'start' |
93
112
  data-slot="top-bar-section"
94
113
  class={cn(
95
114
  'window-no-drag flex min-w-0 items-center gap-2 overflow-x-auto overflow-y-hidden',
96
- local.align === 'end' && 'md:justify-end',
115
+ local.align === 'end' && 'md:justify-end-safe',
97
116
  local.align !== 'end' && 'justify-start',
98
117
  local.class
99
118
  )}