gestalt-mobile 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +42 -11
  2. package/dist/client/assets/index-C5qFcFUg.css +1 -0
  3. package/dist/client/assets/index-tMyOolAm.js +24 -0
  4. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  5. package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  6. package/dist/client/assets/jetbrains-mono-cyrillic-ext-400-normal-Bh0R7Dhr.woff +0 -0
  7. package/dist/client/assets/jetbrains-mono-cyrillic-ext-400-normal-C_uLvvQ5.woff2 +0 -0
  8. package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  9. package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  10. package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  11. package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  12. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  13. package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  14. package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-ByoDsISC.woff2 +0 -0
  15. package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  16. package/dist/client/assets/syne-greek-600-normal-BJs4kvyH.woff +0 -0
  17. package/dist/client/assets/syne-greek-600-normal-BpFII5Ja.woff2 +0 -0
  18. package/dist/client/assets/syne-latin-600-normal-BiwQbQXw.woff2 +0 -0
  19. package/dist/client/assets/syne-latin-600-normal-dRu9QuIh.woff +0 -0
  20. package/dist/client/assets/syne-latin-ext-600-normal-DEzBMW8B.woff +0 -0
  21. package/dist/client/assets/syne-latin-ext-600-normal-ZnizrDKU.woff2 +0 -0
  22. package/dist/client/index.html +3 -2
  23. package/dist/server/server/cli.js +3 -1
  24. package/dist/server/server/composition.js +25 -21
  25. package/dist/server/server/features/auth/devices/revoke/endpoint.js +3 -1
  26. package/dist/server/server/features/auth/enrollment-tickets/endpoint.js +7 -2
  27. package/dist/server/server/features/auth/enrollment-tickets/status/endpoint.js +3 -1
  28. package/dist/server/server/features/auth/http/cookies.js +12 -2
  29. package/dist/server/server/features/auth/register/verify/endpoint.js +70 -15
  30. package/dist/server/server/features/plans/application/measurement-snapshot.js +1 -1
  31. package/dist/server/server/features/plans/get-measurement/endpoint.js +1 -3
  32. package/dist/server/server/features/sessions/interaction/kind.js +7 -1
  33. package/dist/server/server/features/sessions/list-recent-threads/endpoint.js +3 -1
  34. package/dist/server/server/features/sessions/model/relay-session.js +3 -1
  35. package/dist/server/server/features/sessions/start-session/use-case.js +1 -1
  36. package/dist/server/server/features/skills/delete-profile/endpoint.js +12 -3
  37. package/dist/server/server/features/skills/list-available/endpoint.js +24 -6
  38. package/dist/server/server/features/skills/list-profiles/endpoint.js +29 -5
  39. package/dist/server/server/features/skills/model/skill-profile.js +23 -3
  40. package/dist/server/server/platform/auth/simple-webauthn-adapter.js +23 -3
  41. package/dist/server/server/platform/catalog/profile-command.js +8 -1
  42. package/dist/server/server/platform/codex/normalizer.js +22 -4
  43. package/dist/server/server/platform/codex/session-runtime.js +52 -11
  44. package/dist/server/server/platform/codex/thread-plan-name.js +5 -2
  45. package/dist/server/server/platform/http/problem-reply.js +4 -1
  46. package/dist/server/server/platform/plans/filesystem-plan-status-source.js +11 -6
  47. package/dist/server/server/platform/plans/filesystem-workspace-plan-catalog.js +4 -1
  48. package/dist/server/server/platform/plans/plan-measurement-command.js +4 -1
  49. package/dist/server/server/platform/skills/codex-skill-catalog.js +29 -7
  50. package/dist/server/server/platform/skills/filesystem-skill-profile-store.js +5 -1
  51. package/dist/server/shared/contracts/quiz.js +30 -9
  52. package/package.json +3 -1
  53. package/dist/client/assets/index-CMs5ZzxJ.css +0 -1
  54. package/dist/client/assets/index-D2sT9ZHY.js +0 -19
@@ -36,18 +36,34 @@ export function decodeNotification(input) {
36
36
  if (input.method === 'item/agentMessage/delta') {
37
37
  const params = record(input.params);
38
38
  return typeof params?.delta === 'string' && params.delta.length <= 64_000
39
- ? { method: input.method, params: { delta: params.delta } } : null;
39
+ ? { method: input.method, params: { delta: params.delta } }
40
+ : null;
40
41
  }
41
42
  if (input.method === 'turn/completed') {
42
43
  const params = record(input.params);
43
44
  const turn = record(params?.turn);
44
45
  if (!turn || typeof turn.id !== 'string' || turn.id.length > 256)
45
46
  return null;
46
- return { method: input.method, params: { ...(typeof params?.threadId === 'string' && params.threadId.length <= 256 ? { threadId: params.threadId } : {}), turn: { id: turn.id, ...(typeof turn.status === 'string' && turn.status.length <= 64 ? { status: turn.status } : {}) } } };
47
+ return {
48
+ method: input.method,
49
+ params: {
50
+ ...(typeof params?.threadId === 'string' && params.threadId.length <= 256
51
+ ? { threadId: params.threadId }
52
+ : {}),
53
+ turn: {
54
+ id: turn.id,
55
+ ...(typeof turn.status === 'string' && turn.status.length <= 64
56
+ ? { status: turn.status }
57
+ : {}),
58
+ },
59
+ },
60
+ };
47
61
  }
48
62
  if (input.method === 'item/started' || input.method === 'item/completed') {
49
63
  const params = record(input.params);
50
- return params && 'item' in params ? { method: input.method, params: { item: params.item } } : null;
64
+ return params && 'item' in params
65
+ ? { method: input.method, params: { item: params.item } }
66
+ : null;
51
67
  }
52
68
  return null;
53
69
  }
@@ -103,5 +119,7 @@ function reasoningSummary(parts) {
103
119
  });
104
120
  }
105
121
  function record(value) {
106
- return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
122
+ return value && typeof value === 'object' && !Array.isArray(value)
123
+ ? value
124
+ : null;
107
125
  }
@@ -47,8 +47,12 @@ class SessionResource {
47
47
  this.onDisposed();
48
48
  return true;
49
49
  }
50
- get active() { return !this.disposed; }
51
- attach(unregister) { this.unregister = unregister; }
50
+ get active() {
51
+ return !this.disposed;
52
+ }
53
+ attach(unregister) {
54
+ this.unregister = unregister;
55
+ }
52
56
  }
53
57
  export class CodexSessionRuntime {
54
58
  launch;
@@ -149,7 +153,10 @@ export class CodexSessionRuntime {
149
153
  throw new Error('CODEX_SESSION_NOT_RUNNING');
150
154
  const [rateLimits, thread] = await Promise.all([
151
155
  resource.process.rpc.request('account/rateLimits/read', {}),
152
- resource.process.rpc.request('thread/read', { threadId: session.threadId, includeTurns: true }),
156
+ resource.process.rpc.request('thread/read', {
157
+ threadId: session.threadId,
158
+ includeTurns: true,
159
+ }),
153
160
  ]);
154
161
  return createPlanMeasurementSnapshot({
155
162
  capturedAt: new Date().toISOString(),
@@ -208,7 +215,9 @@ export class CodexSessionRuntime {
208
215
  catch (error) {
209
216
  if (!canRebindMissingRollout(session, error))
210
217
  throw error;
211
- const replacementThreadId = await this.startThread(resource.process, session, { model: session.model });
218
+ const replacementThreadId = await this.startThread(resource.process, session, {
219
+ model: session.model,
220
+ });
212
221
  result = rebindMissingRollout(session, error, replacementThreadId, now);
213
222
  }
214
223
  resource.threadId = result.session.threadId;
@@ -234,7 +243,11 @@ export class CodexSessionRuntime {
234
243
  const resource = this.sessions.get(sessionId);
235
244
  const threadId = resource?.threadId;
236
245
  const name = resource?.pendingThreadName;
237
- if (!resource || !threadId || !name || resource.writtenThreadName === name || resource.capabilities.get('thread/name/set') === false)
246
+ if (!resource ||
247
+ !threadId ||
248
+ !name ||
249
+ resource.writtenThreadName === name ||
250
+ resource.capabilities.get('thread/name/set') === false)
238
251
  return;
239
252
  try {
240
253
  await resource.process.rpc.request('thread/name/set', { threadId, name });
@@ -286,7 +299,7 @@ export class CodexSessionRuntime {
286
299
  profile: session.profile,
287
300
  cwd: session.workspacePath,
288
301
  skillsConfig: await this.resolveSkills?.(session),
289
- ...((lease || this.planMeasurementBaseUrl)
302
+ ...(lease || this.planMeasurementBaseUrl
290
303
  ? {
291
304
  environment: {
292
305
  ...(lease
@@ -347,7 +360,12 @@ function decodeRateLimits(value) {
347
360
  return undefined;
348
361
  const decoded = limits.flatMap((limit) => {
349
362
  const record = asRecord(limit);
350
- return typeof record?.windowDurationMins === 'number' && Number.isFinite(record.windowDurationMins) && typeof record.usedPercent === 'number' && Number.isFinite(record.usedPercent) ? [{ windowDurationMins: record.windowDurationMins, usedPercent: record.usedPercent }] : [];
363
+ return typeof record?.windowDurationMins === 'number' &&
364
+ Number.isFinite(record.windowDurationMins) &&
365
+ typeof record.usedPercent === 'number' &&
366
+ Number.isFinite(record.usedPercent)
367
+ ? [{ windowDurationMins: record.windowDurationMins, usedPercent: record.usedPercent }]
368
+ : [];
351
369
  });
352
370
  return decoded.length === limits.length ? { rateLimits: decoded } : undefined;
353
371
  }
@@ -396,10 +414,18 @@ function decodeThreadRead(value) {
396
414
  const record = asRecord(turn);
397
415
  if (!record)
398
416
  throw new Error('CODEX_THREAD_READ_MALFORMED');
399
- return { id: boundedString(record.id), status: boundedString(record.status), startedAt: typeof record.startedAt === 'number' ? record.startedAt : undefined, completedAt: typeof record.completedAt === 'number' ? record.completedAt : undefined, items: Array.isArray(record.items) ? record.items.flatMap(decodeHistoryItem) : undefined };
417
+ return {
418
+ id: boundedString(record.id),
419
+ status: boundedString(record.status),
420
+ startedAt: typeof record.startedAt === 'number' ? record.startedAt : undefined,
421
+ completedAt: typeof record.completedAt === 'number' ? record.completedAt : undefined,
422
+ items: Array.isArray(record.items) ? record.items.flatMap(decodeHistoryItem) : undefined,
423
+ };
400
424
  });
401
425
  }
402
- function boundedString(value, max = 64_000) { return typeof value === 'string' && value.length <= max ? value : undefined; }
426
+ function boundedString(value, max = 64_000) {
427
+ return typeof value === 'string' && value.length <= max ? value : undefined;
428
+ }
403
429
  function decodeHistoryItem(value) {
404
430
  const item = asRecord(value);
405
431
  const id = boundedString(item?.id, 256);
@@ -407,10 +433,25 @@ function decodeHistoryItem(value) {
407
433
  if (!item || !id || !type)
408
434
  return [];
409
435
  if (type === 'agentMessage')
410
- return [{ id, type, ...(boundedString(item.text) ? { text: boundedString(item.text) } : {}), ...(boundedString(item.phase, 64) ? { phase: boundedString(item.phase, 64) } : {}) }];
436
+ return [
437
+ {
438
+ id,
439
+ type,
440
+ ...(boundedString(item.text) ? { text: boundedString(item.text) } : {}),
441
+ ...(boundedString(item.phase, 64) ? { phase: boundedString(item.phase, 64) } : {}),
442
+ },
443
+ ];
411
444
  if (type === 'plan' && boundedString(item.text))
412
445
  return [{ id, type, text: boundedString(item.text) }];
413
446
  if (type === 'commandExecution' && boundedString(item.command) && boundedString(item.status, 64))
414
- return [{ id, type, command: boundedString(item.command), status: boundedString(item.status, 64), ...(typeof item.exitCode === 'number' ? { exitCode: item.exitCode } : {}) }];
447
+ return [
448
+ {
449
+ id,
450
+ type,
451
+ command: boundedString(item.command),
452
+ status: boundedString(item.status, 64),
453
+ ...(typeof item.exitCode === 'number' ? { exitCode: item.exitCode } : {}),
454
+ },
455
+ ];
415
456
  return [];
416
457
  }
@@ -12,12 +12,15 @@ export function threadPlanName(plan, limit = 120) {
12
12
  ? `L1 ${l1.indexOf(active) + 1}/${l1.length}`
13
13
  : awaiting
14
14
  ? `Review ${l1.indexOf(awaiting) + 1}/${l1.length}`
15
- : l1.length > 0 && l1.every((step) => step.state === 'DONE' && step.reviewStatus === 'REVIEWED')
15
+ : l1.length > 0 &&
16
+ l1.every((step) => step.state === 'DONE' && step.reviewStatus === 'REVIEWED')
16
17
  ? 'Complete'
17
18
  : 'Authoring';
18
19
  return `${truncateTitle(plan.title, Math.max(1, limit - suffix.length - 3))} — ${suffix}`;
19
20
  }
20
21
  function truncateTitle(value, limit) {
21
22
  const characters = Array.from(value);
22
- return characters.length <= limit ? value : `${characters.slice(0, Math.max(0, limit - 1)).join('')}…`;
23
+ return characters.length <= limit
24
+ ? value
25
+ : `${characters.slice(0, Math.max(0, limit - 1)).join('')}…`;
23
26
  }
@@ -6,5 +6,8 @@
6
6
  import { problem } from './problem.js';
7
7
  /** Send the established RFC 7807-compatible response without deciding its semantics. */
8
8
  export function sendProblem(reply, code, status, detail) {
9
- return reply.code(status).type('application/problem+json').send(problem(code, status, detail));
9
+ return reply
10
+ .code(status)
11
+ .type('application/problem+json')
12
+ .send(problem(code, status, detail));
10
13
  }
@@ -29,10 +29,7 @@ export class FilesystemPlanStatusSource {
29
29
  await mkdir(this.dismissalDirectory, { recursive: true, mode: 0o700 });
30
30
  await mkdir(statusDirectory, { recursive: true, mode: 0o700 });
31
31
  if (process.platform !== 'win32') {
32
- await Promise.all([
33
- chmod(this.dismissalDirectory, 0o700),
34
- chmod(statusDirectory, 0o700),
35
- ]);
32
+ await Promise.all([chmod(this.dismissalDirectory, 0o700), chmod(statusDirectory, 0o700)]);
36
33
  }
37
34
  const lease = new ActiveLease(statusDirectory, session.id, session.workspacePath, listener, this.planReadFilesystem, (identity) => this.isDismissed(session.id, identity), (statusPath) => this.activeStatusPaths.set(session.id, statusPath), () => {
38
35
  this.leases.delete(session.id);
@@ -97,7 +94,9 @@ export class FilesystemPlanStatusSource {
97
94
  const next = new Set(dismissed).add(identity);
98
95
  const candidate = join(this.dismissalDirectory, `.${randomUUID()}.dismissals.tmp`);
99
96
  try {
100
- await this.dismissalFilesystem.writeFile(candidate, JSON.stringify([...next]), { mode: 0o600 });
97
+ await this.dismissalFilesystem.writeFile(candidate, JSON.stringify([...next]), {
98
+ mode: 0o600,
99
+ });
101
100
  await this.dismissalFilesystem.rename(candidate, path);
102
101
  }
103
102
  catch (error) {
@@ -272,7 +271,13 @@ class ActiveLease {
272
271
  const previousStatusPath = this.activeStatusPath;
273
272
  this.activeStatusPath = statusPath;
274
273
  this.onActiveStatusPath(statusPath);
275
- const update = { kind: 'updated', plan: result.plan, identity, planPath, reason: signal.reason };
274
+ const update = {
275
+ kind: 'updated',
276
+ plan: result.plan,
277
+ identity,
278
+ planPath,
279
+ reason: signal.reason,
280
+ };
276
281
  const serializedUpdate = JSON.stringify(update);
277
282
  if (serializedUpdate !== this.lastEmittedUpdate) {
278
283
  this.lastEmittedUpdate = serializedUpdate;
@@ -78,7 +78,10 @@ export class FilesystemWorkspacePlanCatalog {
78
78
  return { kind: 'unavailable' };
79
79
  const source = await this.filesystem.readFile(canonical, 'utf8');
80
80
  const after = await this.filesystem.stat(canonical);
81
- if (!after.isFile() || after.size !== before.size || after.ino !== before.ino || after.dev !== before.dev)
81
+ if (!after.isFile() ||
82
+ after.size !== before.size ||
83
+ after.ino !== before.ino ||
84
+ after.dev !== before.dev)
82
85
  return { kind: 'unavailable' };
83
86
  const parsed = parseSupervisedPlan({ source, planPath: canonical, workspacePath });
84
87
  return parsed.kind === 'available' ? parsed : { kind: 'unavailable' };
@@ -9,5 +9,8 @@ const execute = promisify(execFile);
9
9
  export const PLAN_MEASUREMENT_COMMAND_TIMEOUT_MS = 15_000;
10
10
  /** Invokes the explicitly configured Org Plan helper without a shell. */
11
11
  export async function checkpointPlanMeasurement(helperPath, planPath, stepId, snapshot) {
12
- await execute(helperPath, ['measure', 'checkpoint', planPath, stepId, JSON.stringify(snapshot)], { shell: false, timeout: PLAN_MEASUREMENT_COMMAND_TIMEOUT_MS });
12
+ await execute(helperPath, ['measure', 'checkpoint', planPath, stepId, JSON.stringify(snapshot)], {
13
+ shell: false,
14
+ timeout: PLAN_MEASUREMENT_COMMAND_TIMEOUT_MS,
15
+ });
13
16
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { z } from 'zod';
7
7
  import { resolve } from 'node:path';
8
- import { availableSkillSchema } from '../../features/skills/model/skill-profile.js';
8
+ import { availableSkillSchema, } from '../../features/skills/model/skill-profile.js';
9
9
  import { SkillProfileError } from '../../features/skills/model/errors.js';
10
10
  import { launchCodexAppServer } from '../codex/codex-process-launcher.js';
11
11
  const optionalString = z.preprocess((value) => (value === null ? undefined : value), z.string().optional());
@@ -19,12 +19,25 @@ const toolSchema = z.object({
19
19
  url: optionalString,
20
20
  });
21
21
  const wireSkillSchema = z.object({
22
- name: z.string(), description: z.string(), shortDescription: optionalString,
23
- interface: optionalObject(z.object({ displayName: optionalString, shortDescription: optionalString, iconSmall: optionalString, iconLarge: optionalString, brandColor: optionalString, defaultPrompt: optionalString })),
22
+ name: z.string(),
23
+ description: z.string(),
24
+ shortDescription: optionalString,
25
+ interface: optionalObject(z.object({
26
+ displayName: optionalString,
27
+ shortDescription: optionalString,
28
+ iconSmall: optionalString,
29
+ iconLarge: optionalString,
30
+ brandColor: optionalString,
31
+ defaultPrompt: optionalString,
32
+ })),
24
33
  dependencies: optionalObject(z.object({ tools: optionalObject(z.array(toolSchema)) })),
25
- path: z.string(), scope: optionalString, enabled: z.boolean(),
34
+ path: z.string(),
35
+ scope: optionalString,
36
+ enabled: z.boolean(),
37
+ });
38
+ const resultSchema = z.object({
39
+ data: z.array(z.object({ cwd: z.string(), skills: z.array(wireSkillSchema), errors: z.array(z.unknown()) })),
26
40
  });
27
- const resultSchema = z.object({ data: z.array(z.object({ cwd: z.string(), skills: z.array(wireSkillSchema), errors: z.array(z.unknown()) })) });
28
41
  /** Short-lived Codex app-server adapter: initialize, discover, and always terminate. */
29
42
  export class CodexSkillCatalog {
30
43
  profile;
@@ -58,7 +71,14 @@ export class CodexSkillCatalog {
58
71
  });
59
72
  return {
60
73
  skills,
61
- errors: entry.errors.map((error) => ({ message: typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' ? error.message : 'Codex skill discovery error.' })),
74
+ errors: entry.errors.map((error) => ({
75
+ message: typeof error === 'object' &&
76
+ error !== null &&
77
+ 'message' in error &&
78
+ typeof error.message === 'string'
79
+ ? error.message
80
+ : 'Codex skill discovery error.',
81
+ })),
62
82
  };
63
83
  }
64
84
  catch (error) {
@@ -75,7 +95,9 @@ export class CodexSkillCatalog {
75
95
  try {
76
96
  return await Promise.race([
77
97
  promise,
78
- new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('timeout')), this.timeoutMs); }),
98
+ new Promise((_, reject) => {
99
+ timer = setTimeout(() => reject(new Error('timeout')), this.timeoutMs);
100
+ }),
79
101
  ]);
80
102
  }
81
103
  finally {
@@ -55,7 +55,11 @@ export class FilesystemSkillProfileStore {
55
55
  const destination = join(root, `${name}.yml`);
56
56
  const temporary = join(root, `.${name}.${randomUUID()}.tmp`);
57
57
  try {
58
- await writeFile(temporary, serializeSkillProfileYaml(profile), { encoding: 'utf8', mode: 0o600, flag: 'wx' });
58
+ await writeFile(temporary, serializeSkillProfileYaml(profile), {
59
+ encoding: 'utf8',
60
+ mode: 0o600,
61
+ flag: 'wx',
62
+ });
59
63
  await rename(temporary, destination);
60
64
  }
61
65
  finally {
@@ -26,7 +26,12 @@ export const gestaltQuizDynamicTool = {
26
26
  additionalProperties: false,
27
27
  required: ['id', 'header', 'question', 'choices', 'allowCustom'],
28
28
  properties: {
29
- id: { type: 'string', minLength: 1, maxLength: 64, pattern: '^[A-Za-z][A-Za-z0-9_-]*$' },
29
+ id: {
30
+ type: 'string',
31
+ minLength: 1,
32
+ maxLength: 64,
33
+ pattern: '^[A-Za-z][A-Za-z0-9_-]*$',
34
+ },
30
35
  header: { type: 'string', minLength: 1, maxLength: 120 },
31
36
  question: { type: 'string', minLength: 1, maxLength: 600 },
32
37
  choices: {
@@ -61,7 +66,9 @@ export function parseQuiz(value, minimumChoices = 2) {
61
66
  if (questions.some((question) => question === null))
62
67
  return null;
63
68
  const parsed = questions;
64
- return new Set(parsed.map((question) => question.id)).size === parsed.length ? { questions: parsed } : null;
69
+ return new Set(parsed.map((question) => question.id)).size === parsed.length
70
+ ? { questions: parsed }
71
+ : null;
65
72
  }
66
73
  /** Maps Codex's experimental native request shape into the common quiz value. */
67
74
  export function mapNativeUserInputToQuiz(value) {
@@ -87,7 +94,9 @@ export function toQuizToolResponse(answers) {
87
94
  {
88
95
  type: 'input_text',
89
96
  text: JSON.stringify({
90
- answers: Object.fromEntries(answers.filter((answer) => answer.answer.trim()).map((answer) => [answer.id, answer.answer])),
97
+ answers: Object.fromEntries(answers
98
+ .filter((answer) => answer.answer.trim())
99
+ .map((answer) => [answer.id, answer.answer])),
91
100
  }),
92
101
  },
93
102
  ],
@@ -98,7 +107,10 @@ export function isQuizToolResponseForQuiz(quiz, value) {
98
107
  if (!isRecord(value) || value.success !== true || !Array.isArray(value.contentItems))
99
108
  return false;
100
109
  const item = value.contentItems[0];
101
- if (value.contentItems.length !== 1 || !isRecord(item) || item.type !== 'input_text' || typeof item.text !== 'string')
110
+ if (value.contentItems.length !== 1 ||
111
+ !isRecord(item) ||
112
+ item.type !== 'input_text' ||
113
+ typeof item.text !== 'string')
102
114
  return false;
103
115
  try {
104
116
  const response = parseQuizResponse(JSON.parse(item.text));
@@ -109,9 +121,14 @@ export function isQuizToolResponseForQuiz(quiz, value) {
109
121
  }
110
122
  }
111
123
  function parseQuestion(value, minimumChoices) {
112
- if (!isRecord(value) || !Array.isArray(value.choices) || value.choices.length < minimumChoices || value.choices.length > 5)
124
+ if (!isRecord(value) ||
125
+ !Array.isArray(value.choices) ||
126
+ value.choices.length < minimumChoices ||
127
+ value.choices.length > 5)
113
128
  return null;
114
- if (!isQuestionId(value.id) || !isBoundedText(value.header, 120) || !isBoundedText(value.question, 600))
129
+ if (!isQuestionId(value.id) ||
130
+ !isBoundedText(value.header, 120) ||
131
+ !isBoundedText(value.question, 600))
115
132
  return null;
116
133
  if (typeof value.allowCustom !== 'boolean')
117
134
  return null;
@@ -128,7 +145,9 @@ function parseQuestion(value, minimumChoices) {
128
145
  };
129
146
  }
130
147
  function parseChoice(value) {
131
- if (!isRecord(value) || !isBoundedText(value.label, 160) || !isBoundedText(value.description, 600))
148
+ if (!isRecord(value) ||
149
+ !isBoundedText(value.label, 160) ||
150
+ !isBoundedText(value.description, 600))
132
151
  return null;
133
152
  return { label: value.label, description: value.description };
134
153
  }
@@ -136,13 +155,15 @@ function parseQuizResponse(value) {
136
155
  if (!isRecord(value) || !isRecord(value.answers))
137
156
  return null;
138
157
  const entries = Object.entries(value.answers);
139
- if (entries.length === 0 || entries.some(([id, answer]) => !isQuestionId(id) || !isBoundedText(answer, 2_000)))
158
+ if (entries.length === 0 ||
159
+ entries.some(([id, answer]) => !isQuestionId(id) || !isBoundedText(answer, 2_000)))
140
160
  return null;
141
161
  return { answers: Object.fromEntries(entries) };
142
162
  }
143
163
  function isCompleteQuizResponse(quiz, response) {
144
164
  const ids = Object.keys(response.answers);
145
- if (ids.length !== quiz.questions.length || ids.some((id) => !quiz.questions.some((question) => question.id === id)))
165
+ if (ids.length !== quiz.questions.length ||
166
+ ids.some((id) => !quiz.questions.some((question) => question.id === id)))
146
167
  return false;
147
168
  return quiz.questions.every((question) => {
148
169
  const answer = response.answers[question.id];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt-mobile",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Mobile-first web relay for durable Codex development sessions",
5
5
  "keywords": [
6
6
  "codex",
@@ -58,6 +58,8 @@
58
58
  "dependencies": {
59
59
  "@fastify/cookie": "11.1.2",
60
60
  "@fastify/static": "^10.1.0",
61
+ "@fontsource/jetbrains-mono": "5.2.7",
62
+ "@fontsource/syne": "5.2.7",
61
63
  "@simplewebauthn/browser": "13.3.0",
62
64
  "@simplewebauthn/server": "13.3.2",
63
65
  "fastify": "^5.10.0",
@@ -1 +0,0 @@
1
- .session-path.svelte-t9laac{text-align:center;text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;margin:0;overflow:hidden}.session-model.svelte-t9laac{margin-inline-start:clamp(.75rem,3vw,2rem)}@media (width<=34rem){.session-model.svelte-t9laac{display:none}}#chat-activity.svelte-thyolt{--activity-gap:.4rem;--activity-type-color:var(--muted-text,currentColor)}#chat-activity.svelte-thyolt>summary:where(.svelte-thyolt){color:var(--activity-type-color);cursor:pointer;font-size:.875em}.activity-list.svelte-thyolt{gap:var(--activity-gap);margin-block:.25rem 0;margin-inline:0;padding:0;list-style:none;display:grid}.activity-row.svelte-thyolt{flex-wrap:wrap;align-items:baseline;gap:.25rem .5rem;display:flex}.activity-type.svelte-thyolt{color:var(--activity-type-color);font-size:.75em;line-height:1.2}.activity-content.svelte-thyolt{overflow-wrap:anywhere;white-space:pre-wrap;min-inline-size:0}.quiz-form.svelte-77f3gb{gap:1rem;display:grid}fieldset.svelte-77f3gb{border:0;min-inline-size:0;margin:0;padding:0}legend.svelte-77f3gb{gap:.25rem;max-inline-size:100%;display:grid}.choices.svelte-77f3gb{gap:.5rem;margin-block-start:.5rem;display:grid}.choice.svelte-77f3gb{position:relative}.choice.svelte-77f3gb>input:where(.svelte-77f3gb){opacity:0;cursor:pointer;block-size:100%;inline-size:100%;margin:0;position:absolute;inset:0}.choice.svelte-77f3gb>label:where(.svelte-77f3gb){border:2px solid color-mix(in srgb, CanvasText 28%, Canvas);border-radius:.5rem;gap:.125rem;min-block-size:44px;padding:.75rem;display:grid}.choice.svelte-77f3gb>input:where(.svelte-77f3gb):checked+label:where(.svelte-77f3gb){border-width:3px;border-color:highlight}.choice.svelte-77f3gb>input:where(.svelte-77f3gb):focus-visible+label:where(.svelte-77f3gb){outline-offset:2px;outline:3px solid highlight}label.svelte-77f3gb span:where(.svelte-77f3gb),legend.svelte-77f3gb span:where(.svelte-77f3gb){color:color-mix(in srgb, CanvasText 72%, Canvas)}.custom.svelte-77f3gb{margin-block-start:.25rem}.custom.svelte-77f3gb input:where(.svelte-77f3gb){box-sizing:border-box;min-block-size:44px;inline-size:100%;font:inherit;margin-block-start:.375rem}.command-approval-command.svelte-ajsqcq{white-space:pre-wrap;background:color-mix(in srgb, CanvasText 8%, Canvas);border-radius:.375rem;max-inline-size:100%;margin-block:.5rem .75rem;padding:.625rem;overflow-x:auto}.command-approval-missing.svelte-ajsqcq{margin-block:.5rem .75rem}.file-change-approval-targets.svelte-ajsqcq{overflow-wrap:anywhere;margin-block:.5rem .75rem;padding-inline-start:1.5rem}.file-change-approval-missing.svelte-ajsqcq{margin-block:.5rem .75rem}.approval-actions.svelte-ajsqcq{flex-wrap:wrap;gap:clamp(.5rem,2vw,1rem);display:flex}form.svelte-1264sfg>[role=status]:where(.svelte-1264sfg){text-align:start;margin-block:1rem .35rem}.command-menu.svelte-1264sfg{border:1px solid color-mix(in srgb, CanvasText 20%, Canvas);background:canvas;border-radius:.375rem;gap:.125rem;max-inline-size:min(100%,32rem);margin:0 0 .35rem;padding:.25rem;list-style:none;display:grid}.command-menu.svelte-1264sfg button:where(.svelte-1264sfg){inline-size:100%;color:inherit;text-align:start;background:0 0;border:0;border-radius:.25rem;align-items:baseline;gap:.5rem;padding:.25rem .375rem;display:flex}.command-menu.svelte-1264sfg .command-selected:where(.svelte-1264sfg) button:where(.svelte-1264sfg),.command-menu.svelte-1264sfg button:where(.svelte-1264sfg):hover{background:color-mix(in srgb, CanvasText 10%, Canvas)}.command-menu.svelte-1264sfg small:where(.svelte-1264sfg){color:#666;font-size:.8em}.command-menu.svelte-1264sfg button:where(.svelte-1264sfg):focus-visible{outline-offset:1px;outline:2px solid}.prompt-row.svelte-1264sfg{align-items:end;gap:.5rem;display:flex}textarea.svelte-1264sfg{resize:vertical;flex:auto;min-block-size:2.75rem}.prompt-row.svelte-1264sfg button:where(.svelte-1264sfg){flex:none;place-items:center;min-block-size:3rem;inline-size:3rem;padding:0;display:grid}.prompt-row.svelte-1264sfg svg:where(.svelte-1264sfg){fill:none;stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;block-size:1.35rem;inline-size:1.35rem}.block-cursor.svelte-1264sfg{vertical-align:-.1em;background:currentColor;block-size:1em;inline-size:.55ch;animation:1s steps(2,start) infinite svelte-1264sfg-blink;display:inline-block}@media (prefers-reduced-motion:reduce){.block-cursor.svelte-1264sfg{animation:none}}@keyframes svelte-1264sfg-blink{50%{opacity:0}}ol.svelte-1im954t{inline-size:100%;margin:0;padding:0;list-style:none}li.svelte-1im954t{box-sizing:border-box;white-space:pre-wrap;overflow-wrap:anywhere;inline-size:100%;margin-block-end:0}li.svelte-1im954t+li:where(.svelte-1im954t){margin-block-start:1rem}.prompt-turn.svelte-1im954t{background:color-mix(in srgb, Canvas 94%, CanvasText);border-radius:.375rem;padding:.5rem .625rem}.entry-heading.svelte-1im954t{align-items:baseline;gap:.75rem;display:flex}time.svelte-1im954t{color:#666;white-space:nowrap;margin-inline-start:auto;font-size:.875em}.commentary-toggle.svelte-1im954t{color:#666;background:0 0;border:0;min-block-size:1.5rem;padding:0 .125rem;font-size:.875em}.commentary-content.svelte-1im954t{margin-block:.25rem .5rem}.entry-content.svelte-1im954t{margin-block:.125rem 0;margin-inline:0}pre.svelte-1im954t,code.svelte-1im954t{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}pre.svelte-1im954t{white-space:pre;overflow-x:auto}.table-scroll.svelte-1im954t{margin-block:.75rem;overflow-x:auto}table.svelte-1im954t{border-collapse:collapse;white-space:normal;width:max-content;min-width:100%}th.svelte-1im954t,td.svelte-1im954t{text-align:left;vertical-align:top;padding:.25rem .5rem}th.svelte-1im954t{font-weight:600}.toast-viewport.svelte-1a0rutm{z-index:10;pointer-events:none;gap:.5rem;max-block-size:calc(100dvh - 7rem);inline-size:min(24rem,100vw - 1.5rem);display:grid;position:fixed;inset-block-start:calc(1rem + env(safe-area-inset-top));inset-inline-end:max(.75rem, env(safe-area-inset-right));overflow-y:auto}.toast.svelte-1a0rutm{box-sizing:border-box;color:canvastext;border:1px solid canvastext;min-inline-size:0;box-shadow:0 .4rem 1.2rem color-mix(in srgb, CanvasText 24%, transparent);pointer-events:auto;background:canvas;border-inline-start-width:.35rem;border-radius:.55rem;grid-template-columns:auto minmax(0,1fr) 44px;align-items:start;gap:.6rem;padding:.65rem;display:grid}.toast.error.svelte-1a0rutm{border-inline-start-style:double}.toast-symbol.svelte-1a0rutm{box-sizing:border-box;border:2px solid;border-radius:50%;place-items:center;block-size:1.6rem;inline-size:1.6rem;font-weight:800;line-height:1;display:grid}.toast-copy.svelte-1a0rutm{overflow-wrap:anywhere;gap:.15rem;min-inline-size:0;padding-block:.1rem;display:grid}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){text-transform:capitalize}.toast-copy.svelte-1a0rutm small:where(.svelte-1a0rutm){opacity:.72}button.svelte-1a0rutm{min-block-size:44px;inline-size:44px;color:inherit;font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.5rem;display:grid}button.svelte-1a0rutm:hover{background:color-mix(in srgb, CanvasText 10%, transparent)}@media (width<=24rem){.toast.svelte-1a0rutm{grid-template-columns:minmax(0,1fr) 44px;gap:.25rem;max-block-size:5rem;padding:.35rem;overflow-y:auto}.toast-symbol.svelte-1a0rutm{display:none}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;block-size:1px;inline-size:1px;padding:0;position:absolute;overflow:hidden}}@media (forced-colors:active){.toast.svelte-1a0rutm,.toast-symbol.svelte-1a0rutm{border-color:canvastext}}.evidence-page.svelte-1j55xyh{box-sizing:border-box;min-block-size:100dvh;inline-size:100%;padding:1rem 1rem 7rem}h1.svelte-1j55xyh{margin-block:0 .5rem}.evidence-composer.svelte-1j55xyh{position:fixed;inset-block-end:2.75rem;inset-inline:1rem}.evidence-prompt.svelte-1j55xyh{box-sizing:border-box;inline-size:100%;margin-block-start:.35rem;display:block}.evidence-nav.svelte-1j55xyh{padding:.5rem 1rem calc(.5rem + env(safe-area-inset-bottom));background:canvas;border-block-start:1px solid canvastext;grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.evidence-nav.svelte-1j55xyh button:where(.svelte-1j55xyh){text-overflow:ellipsis;min-block-size:44px;min-inline-size:0;overflow:hidden}@media (width<=24rem){.evidence-page.svelte-1j55xyh{padding:.5rem .5rem 7rem}.evidence-composer.svelte-1j55xyh{inset-inline:.5rem}}.filesystem-tree.svelte-6z3q4v{inline-size:100%;max-inline-size:100%;display:grid;overflow-x:clip}.tree-row.svelte-6z3q4v{min-inline-size:0;align-items:stretch;padding-inline-start:min(calc(var(--tree-level) * .75rem), 4.5rem);display:flex}.disclosure.svelte-6z3q4v,.disclosure-spacer.svelte-6z3q4v{flex:0 0 44px;min-block-size:44px;inline-size:44px}.disclosure.svelte-6z3q4v{color:inherit;font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.25rem;font-weight:700;display:grid}.disclosure.svelte-6z3q4v:hover{background:color-mix(in srgb, CanvasText 10%, transparent)}.tree-item.svelte-6z3q4v{min-block-size:44px;min-inline-size:0;color:inherit;font:inherit;text-align:start;background:0 0;border:1px solid #0000;border-radius:.35rem;flex:auto;align-items:center;gap:.5rem;padding:.3rem .5rem;display:flex;overflow:hidden}.tree-item.svelte-6z3q4v:hover{background:color-mix(in srgb, CanvasText 8%, transparent)}.tree-item.selected.svelte-6z3q4v{background:color-mix(in srgb, Highlight 16%, Canvas);border-color:currentColor;font-weight:650}.tree-item[aria-disabled=true].svelte-6z3q4v{opacity:.58}.tree-item.repository.svelte-6z3q4v{border-inline-start-width:3px}.selection-mark.svelte-6z3q4v{text-align:center;flex:0 0 1rem;inline-size:1rem}.node-copy.svelte-6z3q4v{flex:auto;min-inline-size:0;display:grid}.node-name.svelte-6z3q4v,.node-path.svelte-6z3q4v{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}.node-path.svelte-6z3q4v{opacity:.72;font-size:.75em;font-weight:400}.repository-badge.svelte-6z3q4v{border:1px solid;border-radius:999px;flex:none;padding:.1rem .35rem;font-size:.72em;font-weight:700;line-height:1.2}@media (width<=24rem){.tree-row.svelte-6z3q4v{padding-inline-start:min(calc(var(--tree-level) * .25rem), 1.5rem)}.tree-item.svelte-6z3q4v{gap:.35rem;padding-inline:.35rem}.repository-badge.svelte-6z3q4v{display:none}}@media (forced-colors:active){.tree-item.selected.svelte-6z3q4v,.tree-item.repository.svelte-6z3q4v,.repository-badge.svelte-6z3q4v{border-color:canvastext}}.evidence-shell.svelte-1d6w4ti{box-sizing:border-box;inline-size:min(100%,48rem);min-inline-size:0;margin-inline:auto;padding:1rem}.eyebrow.svelte-1d6w4ti{letter-spacing:.08em;text-transform:uppercase;margin:0;font-size:.8rem;font-weight:750}h1.svelte-1d6w4ti{margin-block:.25rem .5rem;font-size:clamp(1.35rem,6vw,2rem);line-height:1.15}.instructions.svelte-1d6w4ti{max-inline-size:42rem;margin-block:0 1rem}.tree-panel.svelte-1d6w4ti{border:1px solid color-mix(in srgb, CanvasText 35%, transparent);border-radius:.6rem;min-inline-size:0;padding:.5rem}@media (width<=24rem){.evidence-shell.svelte-1d6w4ti{padding:.5rem}.tree-panel.svelte-1d6w4ti{padding:.25rem}}.git-view.svelte-pudr3t{gap:1.5rem;inline-size:100%;min-inline-size:0;display:grid}.git-tree.svelte-pudr3t,.repository-details.svelte-pudr3t{min-inline-size:0}.section-heading.svelte-pudr3t h3:where(.svelte-pudr3t),.repository-details.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block:0 .35rem}.section-heading.svelte-pudr3t p:where(.svelte-pudr3t){color:color-mix(in srgb, CanvasText 70%, Canvas);margin-block:0 .75rem}.clone-form.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.clone-controls.svelte-pudr3t{grid-template-columns:minmax(10rem,.45fr) minmax(0,1fr) auto;align-items:end;gap:.5rem;display:grid}.clone-field.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.field-label.svelte-pudr3t{font-weight:600}.clone-destination.svelte-pudr3t output:where(.svelte-pudr3t){overflow-wrap:anywhere;border:1px solid color-mix(in srgb, CanvasText 35%, Canvas);border-radius:.35rem;min-block-size:2.75rem;padding:.65rem .75rem}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:5.75rem}.clone-form.svelte-pudr3t p:where(.svelte-pudr3t){color:color-mix(in srgb, CanvasText 70%, Canvas);margin:0;font-size:.875rem}.clone-status.svelte-pudr3t{font-weight:600;color:canvastext!important}.git-overview.svelte-pudr3t{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:1rem;display:grid}.git-status.svelte-pudr3t p:where(.svelte-pudr3t){margin-block:.5rem}.git-actions.svelte-pudr3t{gap:.5rem;display:grid}.git-actions.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:6rem}.commit-history.svelte-pudr3t{margin-block-start:2rem}.commit-history.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block-end:.75rem}.commit-list.svelte-pudr3t{gap:.5rem;margin:0;padding:0;list-style:none;display:grid}.commit-entry.svelte-pudr3t{border-block-end:1px solid color-mix(in srgb, CanvasText 20%, Canvas);gap:.35rem;min-inline-size:0;padding:.8rem 0;display:grid}.commit-subject.svelte-pudr3t,.commit-meta.svelte-pudr3t{gap:.65rem;min-inline-size:0;display:flex}.commit-subject.svelte-pudr3t{overflow-wrap:anywhere;align-items:baseline;font-weight:600}.commit-subject.svelte-pudr3t code:where(.svelte-pudr3t){color:color-mix(in srgb, CanvasText 75%, Canvas);flex:none;font-size:.875em}.commit-meta.svelte-pudr3t{color:color-mix(in srgb, CanvasText 70%, Canvas);flex-wrap:wrap;justify-content:space-between;font-size:.875rem}.commit-meta.svelte-pudr3t time:where(.svelte-pudr3t){overflow-wrap:anywhere;min-inline-size:0;max-inline-size:100%}@media (width<=28rem){.clone-controls.svelte-pudr3t{grid-template-columns:1fr}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){inline-size:100%}}.plan.svelte-3rmyvg{overflow-wrap:anywhere;min-inline-size:0}header.svelte-3rmyvg{justify-content:space-between;align-items:start;gap:1rem;display:flex}h2.svelte-3rmyvg,p.svelte-3rmyvg{margin-block:.4rem}progress.svelte-3rmyvg{inline-size:100%}ol.svelte-3rmyvg{padding-inline-start:1.25rem}details.svelte-3rmyvg{margin-block:.5rem}summary.svelte-3rmyvg{cursor:pointer;gap:.2rem;display:grid}.measurement.svelte-3rmyvg{color:var(--muted,currentColor);font-variant-numeric:tabular-nums}.metadata.svelte-3rmyvg{gap:.25rem;display:grid}.metadata.svelte-3rmyvg div:where(.svelte-3rmyvg){gap:.5rem;display:flex}.metadata.svelte-3rmyvg dd:where(.svelte-3rmyvg){margin:0}.close.svelte-3rmyvg{flex:none;block-size:2.75rem;inline-size:2.75rem;font-size:1.5rem}button.svelte-3rmyvg:focus-visible,summary.svelte-3rmyvg:focus-visible{outline-offset:2px;outline:3px solid}@media (prefers-reduced-motion:reduce){.svelte-3rmyvg{scroll-behavior:auto}}.plans.svelte-x2ko9l{overflow-wrap:anywhere;min-inline-size:0}h2.svelte-x2ko9l{margin-block:.4rem}ul.svelte-x2ko9l{gap:.5rem;margin:0;padding:0;list-style:none;display:grid}button.svelte-x2ko9l{text-align:start;gap:.2rem;min-block-size:2.75rem;inline-size:100%;padding:.75rem;display:grid}button.svelte-x2ko9l:focus-visible{outline-offset:2px;outline:3px solid}.session-list.svelte-hxb22h{gap:1rem;inline-size:100%;margin-block:0 1.5rem;padding-inline:0;list-style:none;display:grid}.current-session.svelte-hxb22h{outline:2px solid var(--accent,#4f46e5);outline-offset:2px}.session-select.svelte-hxb22h{inline-size:100%;color:inherit;font:inherit;text-align:inherit;cursor:pointer;background:0 0;border:0;padding:0;display:block}.managed-session.svelte-hxb22h{box-sizing:border-box;border:1px solid canvastext;border-radius:.5rem;grid-template-columns:max-content minmax(0,1fr);align-items:start;gap:.75rem;inline-size:100%;padding:.75rem;display:grid}.open-session.svelte-hxb22h{background:color-mix(in srgb, Highlight 12%, Canvas);border-inline-start:.3rem solid highlight}.session-details.svelte-hxb22h{min-inline-size:0}.workspace-path.svelte-hxb22h{overflow-wrap:anywhere}.profile-badge.svelte-hxb22h{background:color-mix(in srgb, Highlight 14%, Canvas);color:canvastext;overflow-wrap:anywhere;border-radius:999px;margin-block-start:.35rem;padding:.15rem .4rem;font-size:.875rem;display:inline-block}.org-plan-metadata.svelte-hxb22h{gap:.125rem;margin-block-start:.35rem;display:grid}.org-plan-title.svelte-hxb22h{font-weight:600}.org-plan-filename.svelte-hxb22h{overflow-wrap:anywhere;color:color-mix(in srgb, CanvasText 72%, Canvas)}.session-actions.svelte-hxb22h{flex-wrap:wrap;gap:.5rem;display:flex}@media (width<=30rem){.managed-session.svelte-hxb22h{grid-template-columns:minmax(0,1fr)}}.recent-session.svelte-hxb22h{grid-template-columns:minmax(0,1fr) max-content;align-items:start;gap:.75rem;display:grid}.session-base.svelte-hxb22h{gap:.65rem;min-inline-size:0;margin-block:1rem;display:grid}.session-base-heading.svelte-hxb22h h3:where(.svelte-hxb22h),.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin:0}.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin-block-start:.25rem}.tree-panel.svelte-hxb22h{box-sizing:border-box;overscroll-behavior:contain;scrollbar-gutter:stable;border:1px solid color-mix(in srgb, CanvasText 35%, transparent);border-radius:.6rem;max-block-size:min(22rem,48vh);min-inline-size:0;padding:.35rem;overflow-y:auto}.session-settings.svelte-hxb22h{gap:.5rem;min-inline-size:0;margin-block-end:.75rem;display:grid}.session-setting-labels.svelte-hxb22h,.session-setting-controls.svelte-hxb22h{grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;min-inline-size:0;display:grid}.session-setting-labels.svelte-hxb22h label:where(.svelte-hxb22h){overflow-wrap:anywhere;min-inline-size:0}.session-setting-controls.svelte-hxb22h select:where(.svelte-hxb22h){text-overflow:ellipsis;inline-size:100%;min-inline-size:0;max-inline-size:100%;overflow:hidden}.session-secondary-actions.svelte-hxb22h,.model-control.svelte-hxb22h{align-items:center;gap:.5rem;min-inline-size:0;display:flex}.session-secondary-actions.svelte-hxb22h{flex-wrap:wrap}.model-control.svelte-hxb22h{margin-inline-start:auto}.skills-profile-error.svelte-hxb22h{color:#8a1c14;margin:0;font-size:.875rem}.new-session-button.svelte-hxb22h{color:canvas;inline-size:auto;box-shadow:inset 0 .15rem 0 color-mix(in srgb, Canvas 45%, transparent);background:canvastext;border-color:canvastext;font-weight:700}@media (width<=28rem){.tree-panel.svelte-hxb22h{padding:.2rem}.model-control.svelte-hxb22h{margin-inline-start:0}}.skills-view.svelte-dg6sjj{min-inline-size:0;padding:1rem max(1rem, env(safe-area-inset-right)) calc(5rem + env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left))}.intro.svelte-dg6sjj,.save-intent.svelte-dg6sjj,.summary.svelte-dg6sjj{max-inline-size:70ch}form.svelte-dg6sjj,.field-grid.svelte-dg6sjj{gap:.65rem;display:grid}.field-grid.svelte-dg6sjj{grid-template-columns:minmax(0,1fr)}label.svelte-dg6sjj{font-weight:650}select.svelte-dg6sjj,input.svelte-dg6sjj,button.svelte-dg6sjj{box-sizing:border-box;min-block-size:3rem;inline-size:100%;font:inherit}dialog.svelte-dg6sjj{max-inline-size:min(100% - 2rem,32rem)}.dialog-actions.svelte-dg6sjj{gap:.65rem;display:grid}input.svelte-dg6sjj,select.svelte-dg6sjj{padding-inline:.7rem;font-size:1rem}button.svelte-dg6sjj{padding-inline:1rem}.profile-actions.svelte-dg6sjj{grid-template-columns:repeat(2,minmax(0,1fr));gap:.65rem;display:grid}.skills-toolbar.svelte-dg6sjj{flex-wrap:wrap;align-items:center;gap:.65rem;margin-block:1rem;display:flex}.refresh-skills.svelte-dg6sjj{flex:none;min-block-size:2.25rem;inline-size:auto;padding-inline:.65rem}.refresh-skills.svelte-dg6sjj span:where(.svelte-dg6sjj):first-child{font-size:1.2em;line-height:1}.summary.svelte-dg6sjj{overflow-wrap:anywhere;min-inline-size:0;margin:0}.notice.svelte-dg6sjj{border-inline-start:.3rem solid #976600;padding-inline-start:.7rem}.error.svelte-dg6sjj{color:#8a1c14;border-inline-start:.3rem solid #b42318;padding-inline-start:.7rem}.skill-list.svelte-dg6sjj{gap:.75rem;padding:0;list-style:none;display:grid}.skill-card.svelte-dg6sjj{border:1px solid color-mix(in srgb, var(--brand-color,currentColor) 35%, transparent);border-radius:.5rem;min-inline-size:0;padding:.75rem;scroll-margin-block:1rem}.skill-toggle.svelte-dg6sjj{flex-wrap:wrap;align-items:center;gap:.65rem;min-block-size:3rem;display:flex}.skill-toggle.svelte-dg6sjj>span:where(.svelte-dg6sjj){overflow-wrap:anywhere;min-inline-size:0}.skill-toggle.svelte-dg6sjj input:where(.svelte-dg6sjj){flex:none;min-block-size:1.25rem;inline-size:1.25rem}.state.svelte-dg6sjj{margin-inline-start:auto;font-weight:500}.skill-details.svelte-dg6sjj{overflow-wrap:anywhere;padding-inline-start:1.9rem}.skill-details.svelte-dg6sjj>:where(.svelte-dg6sjj):first-child{margin-block-start:.25rem}.skill-details.svelte-dg6sjj>:where(.svelte-dg6sjj):last-child{margin-block-end:0}dl.svelte-dg6sjj{grid-template-columns:minmax(7rem,auto) minmax(0,1fr);gap:.45rem .75rem;display:grid}dt.svelte-dg6sjj{font-weight:650}dd.svelte-dg6sjj{min-inline-size:0;margin:0}dd.svelte-dg6sjj ul:where(.svelte-dg6sjj){margin:0;padding-inline-start:1.25rem}.path.svelte-dg6sjj{overflow-wrap:anywhere}.svelte-dg6sjj:where(button:where(.svelte-dg6sjj),input:where(.svelte-dg6sjj),select:where(.svelte-dg6sjj)):focus-visible{outline-offset:2px;outline:3px solid #1261a0}@media (prefers-color-scheme:dark){.error.svelte-dg6sjj{color:#ffb4ab}.notice.svelte-dg6sjj{color:#ffd8a8}}@media (width>=42rem){.field-grid.svelte-dg6sjj{grid-template-columns:minmax(10rem,1fr) minmax(0,2fr);align-items:center}}@media (width<=30rem){dl.svelte-dg6sjj{grid-template-columns:1fr;gap:.2rem}}.devices.svelte-1nygetn{max-inline-size:48rem;margin:auto;padding:1rem}.devices.svelte-1nygetn header:where(.svelte-1nygetn){align-items:center;gap:1rem;display:flex}.devices.svelte-1nygetn ul:where(.svelte-1nygetn){padding:0;list-style:none}.devices.svelte-1nygetn li:where(.svelte-1nygetn),section.svelte-1nygetn{border:1px solid canvastext;border-radius:.5rem;margin-block:1rem;padding:1rem}.devices.svelte-1nygetn form:where(.svelte-1nygetn){gap:.65rem;display:grid}.devices.svelte-1nygetn p:where(.svelte-1nygetn){overflow-wrap:anywhere;margin:0}.devices.svelte-1nygetn input:where(.svelte-1nygetn),.devices.svelte-1nygetn textarea:where(.svelte-1nygetn),.devices.svelte-1nygetn button:where(.svelte-1nygetn){min-block-size:44px;font:inherit}.devices.svelte-1nygetn input:where(.svelte-1nygetn),.devices.svelte-1nygetn textarea:where(.svelte-1nygetn){min-inline-size:0;padding:.45rem}.devices.svelte-1nygetn textarea:where(.svelte-1nygetn){box-sizing:border-box;overflow-wrap:anywhere;resize:vertical;inline-size:100%}.devices.svelte-1nygetn .actions:where(.svelte-1nygetn){flex-wrap:wrap;gap:.5rem;display:flex}.devices.svelte-1nygetn .actions:where(.svelte-1nygetn) button:where(.svelte-1nygetn){flex:11rem}.devices.svelte-1nygetn img:where(.svelte-1nygetn){background:#fff;inline-size:12rem;max-inline-size:100%;margin-block:.75rem;padding:.5rem;display:block}.feedback.svelte-1nygetn{color:canvastext;font-weight:700}dialog.svelte-1nygetn{color:canvastext;background:canvas;border:1px solid canvastext;border-radius:.5rem;max-inline-size:min(32rem,100% - 2rem)}dialog.svelte-1nygetn form:where(.svelte-1nygetn){flex-wrap:wrap;gap:.5rem;display:flex}@media (forced-colors:active){.devices.svelte-1nygetn li:where(.svelte-1nygetn),section.svelte-1nygetn,dialog.svelte-1nygetn{border-color:canvastext}}@media (prefers-reduced-motion:reduce){.svelte-1nygetn{transition-duration:.01ms!important;animation-duration:.01ms!important}}.chat-view.svelte-1d2e8zz{margin-inline:calc(.25rem - var(--page-inline-padding))}.swipe-surface.svelte-1d2e8zz{touch-action:pan-y}.evidence-mode.svelte-1d2e8zz{box-sizing:border-box;inline-size:100%;min-inline-size:0;padding:0}.session-profile-manager-header.svelte-1d2e8zz{box-sizing:border-box;inline-size:100%;min-inline-size:0;padding-inline:max(1rem, env(safe-area-inset-left)) max(1rem, env(safe-area-inset-right));justify-content:space-between;align-items:safe center;gap:1rem;display:flex}.session-profile-manager-header.svelte-1d2e8zz h2:where(.svelte-1d2e8zz){overflow-wrap:anywhere;min-inline-size:0;margin:0}.close-profile-manager.svelte-1d2e8zz{min-block-size:2.5rem;inline-size:2.5rem;color:inherit;background:0 0;border:0;flex:none;padding:0;font-size:2rem;line-height:1}.enrollment.svelte-1c5qcm6{box-sizing:border-box;inline-size:100%;max-inline-size:none;color:#f5f6ed;background:#10231e;place-items:center;min-height:100dvh;margin:0;padding:1rem;display:grid}.card.svelte-1c5qcm6{box-sizing:border-box;background:#17352c;border:1px solid #88bda7;border-radius:1rem;width:min(100%,42rem);padding:clamp(1.25rem,5vw,3rem)}.brand.svelte-1c5qcm6{color:#b6e6ce;letter-spacing:.08em;text-transform:uppercase;font-weight:700}h1.svelte-1c5qcm6{margin-block:.25rem 1rem;font-size:clamp(1.8rem,7vw,3rem)}.warning.svelte-1c5qcm6{background:#294237;border-left:.3rem solid #f0c96a;padding:1rem}label.svelte-1c5qcm6,input.svelte-1c5qcm6{box-sizing:border-box;width:100%;display:block}label.svelte-1c5qcm6{margin-top:1.5rem;font-weight:700}input.svelte-1c5qcm6,button.svelte-1c5qcm6{min-height:44px;font:inherit}input.svelte-1c5qcm6{margin-top:.4rem;padding:.6rem}button.svelte-1c5qcm6{cursor:pointer;border-radius:.4rem;margin-top:1rem;padding:.55rem 1rem}.authorize.svelte-1c5qcm6{color:#10231e;background:#b6e6ce;width:100%;font-weight:700}.feedback.svelte-1c5qcm6,.error.svelte-1c5qcm6{color:#ffd2c5}.handoff.svelte-1c5qcm6{border-top:1px solid #88bda7;grid-template-columns:minmax(0,1fr);gap:1rem;margin-top:2rem;padding-top:1.5rem;display:grid}.handoff.svelte-1c5qcm6 img:where(.svelte-1c5qcm6){background:#fff;width:12rem;max-width:100%;padding:.5rem}a.svelte-1c5qcm6{color:#b6e6ce;overflow-wrap:anywhere}.copy.svelte-1c5qcm6{display:block}@media (width>=48rem){.handoff.svelte-1c5qcm6{grid-template-columns:12rem minmax(0,1fr);align-items:start}}@media (prefers-reduced-motion:reduce){.svelte-1c5qcm6,.svelte-1c5qcm6:before,.svelte-1c5qcm6:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}.login.svelte-1byekj9{box-sizing:border-box;inline-size:100%;max-inline-size:none;color:#f5f6ed;background:#10231e;place-items:center;min-height:100dvh;margin:0;padding:1rem;display:grid}.card.svelte-1byekj9{box-sizing:border-box;background:#17352c;border:1px solid #88bda7;border-radius:1rem;width:min(100%,32rem);padding:clamp(1.25rem,5vw,3rem)}.brand.svelte-1byekj9{color:#b6e6ce;letter-spacing:.08em;text-transform:uppercase;font-weight:700}h1.svelte-1byekj9{margin-block:.25rem 1rem;font-size:clamp(1.8rem,7vw,3rem)}.sign-in.svelte-1byekj9{color:#10231e;width:100%;min-height:44px;font:inherit;background:#b6e6ce;border-radius:.4rem;margin-top:1rem;padding:.55rem 1rem;font-weight:700}.error.svelte-1byekj9{color:#ffd2c5}@media (prefers-reduced-motion:reduce){.svelte-1byekj9,.svelte-1byekj9:before,.svelte-1byekj9:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}.auth-gate.svelte-y74n0s{max-width:32rem;margin:12vh auto;padding:2rem}button.svelte-y74n0s{margin-right:.75rem}:root{--bottom-navigation-clearance:calc(4rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(5rem + env(safe-area-inset-top));--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;font-family:system-ui,sans-serif}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}body{min-block-size:100dvh;margin:0}.visually-hidden{clip-path:inset(50%)!important;white-space:nowrap!important;border:0!important;block-size:1px!important;inline-size:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}main{--page-inline-padding:1rem;box-sizing:border-box;min-block-size:100dvh;max-inline-size:42rem;padding:calc(1rem + env(safe-area-inset-top)) var(--page-inline-padding) calc(var(--bottom-navigation-clearance) + 1rem);margin-inline:auto}@media (width<=24rem){main{--page-inline-padding:.5rem}}.app-header{z-index:1;margin:calc(-1rem - env(safe-area-inset-top)) calc(.25rem - var(--page-inline-padding)) 1.5rem;padding:calc(1rem + env(safe-area-inset-top)) calc(var(--page-inline-padding) - .25rem) .75rem;background:color-mix(in srgb, Canvas 78%, transparent);-webkit-backdrop-filter:blur(.75rem);backdrop-filter:blur(.75rem);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:1rem;display:flex;position:sticky;inset-block-start:0}.app-header [role=status]{overflow-wrap:anywhere;text-align:end;min-inline-size:0;margin:0}.brand{align-items:center;gap:.4rem;display:flex}.header-actions{flex:none;align-items:center;gap:.25rem;margin-inline-start:auto;display:flex}.weekly-quota{font-variant-numeric:tabular-nums;white-space:nowrap;font-size:.875rem}.menu-trigger{min-block-size:44px;inline-size:44px;color:inherit;background:0 0;border:0;flex:none;place-items:center;padding:0;display:grid}.menu-lines{gap:.25rem;inline-size:1.25rem;display:grid}.menu-lines span{box-sizing:border-box;background:currentColor;block-size:1px;display:block}.menu-lines span:first-child,.menu-lines span:nth-child(2),.menu-lines span:last-child{block-size:2px}@media (width<=28rem){.app-header [role=status]{flex-basis:100%;margin-inline-start:0}}.brand-icon{block-size:2rem;inline-size:2rem}.brand-logotype{block-size:auto;inline-size:6.25rem}.dark-asset,:root[data-theme=dark] .light-asset{display:none}:root[data-theme=dark] .dark-asset{display:initial}@media (prefers-color-scheme:dark){:root:not([data-theme]) .light-asset{display:none}:root:not([data-theme]) .dark-asset{display:initial}}.configuration-panel{color:canvastext;min-inline-size:min(13rem,100vw - 2rem);box-shadow:0 .5rem 1.5rem color-mix(in srgb, CanvasText 25%, transparent);background:canvas;border:1px solid canvastext;border-radius:.5rem;padding:.75rem;position:fixed;inset:4.5rem 1rem auto auto}.configuration-logo{block-size:auto;inline-size:4.5rem;margin-block-end:.5rem;margin-inline-start:auto;display:block}.appearance-control{align-items:center;gap:.5rem;display:flex}.appearance-control span{letter-spacing:-.08em;font-weight:700}.appearance-control select{field-sizing:content;flex:none;min-block-size:2.25rem;inline-size:auto;padding-inline:.5rem}.bottom-navigation{padding:.5rem calc(.25rem + env(safe-area-inset-right)) calc(.5rem + env(safe-area-inset-bottom)) calc(.25rem + env(safe-area-inset-left));background:canvas;border-block-start:1px solid canvastext;grid-template-columns:repeat(4,minmax(0,1fr));gap:.25rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.bottom-navigation button{overflow-wrap:anywhere;word-break:break-word;min-inline-size:0;max-inline-size:100%;padding-inline:0;transition:color .12s,background-color .12s,box-shadow .12s}.bottom-navigation button[aria-pressed=true]{color:canvas;box-shadow:inset 0 .2rem 0 color-mix(in srgb, Canvas 70%, transparent);background:canvastext;font-weight:700}.bottom-navigation button:disabled{color:color-mix(in srgb, CanvasText 40%, transparent);cursor:not-allowed;opacity:.65}@media (width<=30rem){:root{--bottom-navigation-clearance:calc(10rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(14rem + env(safe-area-inset-top))}.bottom-navigation{grid-template-columns:repeat(2,minmax(0,1fr))}.bottom-navigation button{text-align:center;padding-inline:.25rem}}button{min-block-size:44px;font:inherit}.git-view button{scroll-margin-block:var(--sticky-header-clearance) var(--bottom-navigation-clearance)}input,select,textarea{box-sizing:border-box;min-block-size:44px;inline-size:100%;font:inherit;font-size:max(1rem,16px)}:focus-visible{outline-offset:3px;outline:3px solid canvastext}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}@media (forced-colors:active){button,input,select,textarea{border:1px solid canvastext}}