@ogygie/studio 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js
CHANGED
|
@@ -826,6 +826,15 @@ function compositionOfSpec(content) {
|
|
|
826
826
|
const section = next === null ? rest : rest.slice(0, next.index);
|
|
827
827
|
return COMPOSITION_LINE.exec(section)?.[1] ?? null;
|
|
828
828
|
}
|
|
829
|
+
var ASSUMPTIONS_SECTION_TITLE = "Hypoth\xE8ses";
|
|
830
|
+
function assumptionsOfSpec(content) {
|
|
831
|
+
const heading = new RegExp(`^# ${ASSUMPTIONS_SECTION_TITLE}\\s*$`, "mu").exec(content);
|
|
832
|
+
if (heading === null) return [];
|
|
833
|
+
const rest = content.slice(heading.index + heading[0].length);
|
|
834
|
+
const next = /^# /mu.exec(rest);
|
|
835
|
+
const section = next === null ? rest : rest.slice(0, next.index);
|
|
836
|
+
return section.split("\n").map((line) => line.trim()).filter((line) => /^[-*]\s+\S/u.test(line)).map((line) => line.replace(/^[-*]\s+/u, "").trim());
|
|
837
|
+
}
|
|
829
838
|
|
|
830
839
|
// ../ogygie-core/src/proposal.ts
|
|
831
840
|
var DECISION_FILES = ["DECISIONS.md", "DECISIONS.product.md"];
|
|
@@ -1326,6 +1335,16 @@ function parseSpec(filePath, fileName, content) {
|
|
|
1326
1335
|
}
|
|
1327
1336
|
return { ok: true, spec: { frontmatter: checked.data, body: split.body } };
|
|
1328
1337
|
}
|
|
1338
|
+
function intentOf(body) {
|
|
1339
|
+
const heading = /^# @intent\s*$/mu.exec(body);
|
|
1340
|
+
if (heading === null) return "";
|
|
1341
|
+
const rest = body.slice(heading.index + heading[0].length);
|
|
1342
|
+
const next = /^# /mu.exec(rest);
|
|
1343
|
+
const section = (next === null ? rest : rest.slice(0, next.index)).trim();
|
|
1344
|
+
const paragraph = section.split(/\n\s*\n/u)[0]?.replace(/\s+/gu, " ").trim() ?? "";
|
|
1345
|
+
const sentence = /^.*?[.!?](?:\s|$)/u.exec(paragraph)?.[0]?.trim() ?? paragraph;
|
|
1346
|
+
return sentence.length > 160 ? `${sentence.slice(0, 157).trimEnd()}\u2026` : sentence;
|
|
1347
|
+
}
|
|
1329
1348
|
function toReadableSummary(filePath, spec) {
|
|
1330
1349
|
return {
|
|
1331
1350
|
id: spec.frontmatter.id,
|
|
@@ -1333,7 +1352,8 @@ function toReadableSummary(filePath, spec) {
|
|
|
1333
1352
|
priority: spec.frontmatter.priority,
|
|
1334
1353
|
depends_on: spec.frontmatter.depends_on,
|
|
1335
1354
|
filePath,
|
|
1336
|
-
presentSections: presentSectionsOf(spec.body)
|
|
1355
|
+
presentSections: presentSectionsOf(spec.body),
|
|
1356
|
+
intent: intentOf(spec.body)
|
|
1337
1357
|
};
|
|
1338
1358
|
}
|
|
1339
1359
|
async function listSpecFileNames(deps) {
|
|
@@ -1540,7 +1560,9 @@ var ReadableSpecSummarySchema = z6.object({
|
|
|
1540
1560
|
priority: SpecFrontmatterSchema.shape.priority,
|
|
1541
1561
|
depends_on: z6.array(FeatureNameSchema),
|
|
1542
1562
|
filePath: z6.string(),
|
|
1543
|
-
presentSections: z6.array(z6.string())
|
|
1563
|
+
presentSections: z6.array(z6.string()),
|
|
1564
|
+
/** The first sentence of `# @intent`, for a list that reads (v1.20: the target-spec suggestions of the brief). */
|
|
1565
|
+
intent: z6.string().default("")
|
|
1544
1566
|
});
|
|
1545
1567
|
var UnreadableSpecSummarySchema = z6.object({
|
|
1546
1568
|
id: z6.string(),
|
|
@@ -1584,16 +1606,31 @@ var BriefAttachmentSchema = z6.object({
|
|
|
1584
1606
|
name: z6.string().min(1).max(200),
|
|
1585
1607
|
contentBase64: z6.string().min(1).max(57e5)
|
|
1586
1608
|
});
|
|
1609
|
+
var BriefAnswerSchema = z6.object({ questionId: FeatureNameSchema, answer: z6.string().min(1).max(2e3) });
|
|
1587
1610
|
var BriefInputSchema = z6.object({
|
|
1588
1611
|
brief: z6.string().min(50).max(2e4),
|
|
1589
1612
|
/** Absent: the model names the new spec. Present and existing: an amendment. */
|
|
1590
1613
|
specId: FeatureNameSchema.optional(),
|
|
1591
1614
|
/** The client's material — a mock-up, a workbook, a document; five at most, formats md, txt, csv, json, html, xlsx, pdf. */
|
|
1592
|
-
attachments: z6.array(BriefAttachmentSchema).max(5).default([])
|
|
1615
|
+
attachments: z6.array(BriefAttachmentSchema).max(5).default([]),
|
|
1616
|
+
/** D57: the answers of the past rounds of questions, three rounds at most. */
|
|
1617
|
+
answers: z6.array(BriefAnswerSchema).max(15).default([]),
|
|
1618
|
+
/** D57: the round this send opens; at 3 the draft is asked without a question. */
|
|
1619
|
+
round: z6.number().int().min(1).max(4).default(1),
|
|
1620
|
+
/** D57: « Passer les questions » — the draft is asked, the unknown becomes an assumption. */
|
|
1621
|
+
skipQuestions: z6.boolean().default(false)
|
|
1622
|
+
});
|
|
1623
|
+
var BriefQuestionSchema = z6.object({
|
|
1624
|
+
/** kebab-case, stable from one read to the next. */
|
|
1625
|
+
id: FeatureNameSchema,
|
|
1626
|
+
text: z6.string().min(1),
|
|
1627
|
+
/** The proposed answers; a free answer is always possible. */
|
|
1628
|
+
options: z6.array(z6.string().min(1)).min(2).max(4)
|
|
1593
1629
|
});
|
|
1594
1630
|
var BriefOutputSchema = z6.object({
|
|
1595
1631
|
specId: FeatureNameSchema,
|
|
1596
|
-
|
|
1632
|
+
/** 1 when the first draft passed; up to 4 when the model corrected itself and a refused round of questions cost a turn (D57). */
|
|
1633
|
+
attempts: z6.number().int().min(1).max(4),
|
|
1597
1634
|
problemsFixed: z6.array(z6.string()),
|
|
1598
1635
|
/** No feature of the manifest carries this id: its first schemas.ts awaits the operator (D52, lot C). */
|
|
1599
1636
|
isNewFeature: z6.boolean(),
|
|
@@ -1602,10 +1639,30 @@ var BriefOutputSchema = z6.object({
|
|
|
1602
1639
|
compositions: z6.array(z6.string()).default([]),
|
|
1603
1640
|
/** The generator's tally on the draft's `# Génération` block (lines bound without a model, lines carried by a hole); null without a block. */
|
|
1604
1641
|
generation: z6.object({ derivable: z6.number().int().nonnegative(), hole: z6.number().int().nonnegative() }).nullable().default(null),
|
|
1642
|
+
/** D57: the points the model decided that the brief did not say — the `# Hypothèses` lines of the draft. */
|
|
1643
|
+
assumptions: z6.array(z6.string()).default([]),
|
|
1605
1644
|
usage: z6.array(
|
|
1606
1645
|
z6.object({ model: z6.string(), inputTokens: z6.number().int().nonnegative(), outputTokens: z6.number().int().nonnegative() })
|
|
1607
1646
|
)
|
|
1608
1647
|
});
|
|
1648
|
+
var BriefStartOutputSchema = z6.object({ jobId: z6.string() });
|
|
1649
|
+
var BriefStageSchema = z6.enum(["questions", "drafting", "judging", "compositions", "writing", "done", "failed"]);
|
|
1650
|
+
var BriefJobSchema = z6.object({
|
|
1651
|
+
jobId: z6.string(),
|
|
1652
|
+
stage: BriefStageSchema,
|
|
1653
|
+
/** On questions: the round in progress. */
|
|
1654
|
+
round: z6.number().int().min(1).max(3).optional(),
|
|
1655
|
+
/** On drafting / judging: the attempt. */
|
|
1656
|
+
attempt: z6.number().int().min(1).max(4).optional(),
|
|
1657
|
+
/** On compositions: k of N. */
|
|
1658
|
+
composition: z6.object({ done: z6.number().int().nonnegative(), total: z6.number().int().nonnegative() }).optional(),
|
|
1659
|
+
elapsedMs: z6.number().int().nonnegative(),
|
|
1660
|
+
/** The P95 of B3: 90 000 ms. */
|
|
1661
|
+
budgetMs: z6.number().int().positive(),
|
|
1662
|
+
questions: z6.array(BriefQuestionSchema).min(3).max(5).optional(),
|
|
1663
|
+
result: BriefOutputSchema.optional(),
|
|
1664
|
+
error: OgygieErrorSchema.optional()
|
|
1665
|
+
});
|
|
1609
1666
|
var ChecksStateSchema = z6.enum(["green", "red", "pending", "none"]);
|
|
1610
1667
|
var PullRequestStatusSchema = z6.object({
|
|
1611
1668
|
specId: FeatureNameSchema,
|
|
@@ -2031,6 +2088,9 @@ ${link}
|
|
|
2031
2088
|
});
|
|
2032
2089
|
}
|
|
2033
2090
|
|
|
2091
|
+
// src/server/hosted/brief-routes.ts
|
|
2092
|
+
import { z as z10 } from "zod";
|
|
2093
|
+
|
|
2034
2094
|
// src/server/hosted/attachments.ts
|
|
2035
2095
|
import { createHash } from "node:crypto";
|
|
2036
2096
|
import { inflateRawSync } from "node:zlib";
|
|
@@ -2276,6 +2336,9 @@ var MAX_COMPOSITIONS = 3;
|
|
|
2276
2336
|
var COMPOSITION_SEPARATOR = "=== COMPOSITION ===";
|
|
2277
2337
|
var DERIVED_BRIEF_MAX = 6e3;
|
|
2278
2338
|
var BRIEF_IN_MESSAGE_MAX = 2e3;
|
|
2339
|
+
var MAX_QUESTION_ROUNDS = 3;
|
|
2340
|
+
var QUESTIONS_MARKER = /^\s*QUESTIONS\s*$/mu;
|
|
2341
|
+
var NO_MORE_QUESTIONS = "no more questions: write the draft now, and put what you do not know under # Hypoth\xE8ses";
|
|
2279
2342
|
async function readIfPresent(storage, path) {
|
|
2280
2343
|
return await storage.exists(path) ? storage.readFile(path) : null;
|
|
2281
2344
|
}
|
|
@@ -2339,6 +2402,25 @@ async function existingSpecIds(storage) {
|
|
|
2339
2402
|
const entries = await storage.listDirectory("specs");
|
|
2340
2403
|
return entries.map((entry) => entry.path).filter((path) => !path.includes("/") && path.endsWith(".spec.md") && !path.startsWith("_")).map((path) => path.replace(/\.spec\.md$/, "")).sort();
|
|
2341
2404
|
}
|
|
2405
|
+
function extractQuestions(text) {
|
|
2406
|
+
const marker = QUESTIONS_MARKER.exec(text);
|
|
2407
|
+
if (marker === null) return null;
|
|
2408
|
+
const raw = text.slice(marker.index + marker[0].length).replace(/^\s*```(?:json)?\s*/u, "").replace(/\s*```\s*$/u, "").trim();
|
|
2409
|
+
let parsed;
|
|
2410
|
+
try {
|
|
2411
|
+
parsed = JSON.parse(raw);
|
|
2412
|
+
} catch {
|
|
2413
|
+
return { problem: "QUESTIONS: the JSON after the marker does not parse" };
|
|
2414
|
+
}
|
|
2415
|
+
const shape = z9.object({
|
|
2416
|
+
questions: z9.array(z9.object({ id: FeatureNameSchema, text: z9.string().trim().min(1), options: z9.array(z9.string().trim().min(1)).min(2).max(4) })).min(3).max(5)
|
|
2417
|
+
});
|
|
2418
|
+
const checked = shape.safeParse(parsed);
|
|
2419
|
+
if (!checked.success) return { problem: `QUESTIONS: ${checked.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(" ; ")}` };
|
|
2420
|
+
const ids = new Set(checked.data.questions.map((question) => question.id));
|
|
2421
|
+
if (ids.size !== checked.data.questions.length) return { problem: "QUESTIONS: two questions share an id" };
|
|
2422
|
+
return { questions: checked.data.questions };
|
|
2423
|
+
}
|
|
2342
2424
|
function buildBriefPrompt(context) {
|
|
2343
2425
|
const system = [
|
|
2344
2426
|
"Tu r\xE9diges des SPECS de features pour un produit construit avec le framework Ogygie. Une spec est un contrat, relu et sign\xE9 par un humain non d\xE9veloppeur, puis impl\xE9ment\xE9 par une cha\xEEne de g\xE9n\xE9ration : tu \xE9cris pour ce lecteur, en fran\xE7ais, pr\xE9cis, sans jargon.",
|
|
@@ -2358,6 +2440,8 @@ function buildBriefPrompt(context) {
|
|
|
2358
2440
|
"- Tu n'\xE9cris jamais de code ni de design (couleurs, composants) : la spec dit QUOI, jamais COMMENT. Le bloc YAML de `# G\xE9n\xE9ration` n'est pas du code : c'est la d\xE9claration que le g\xE9n\xE9rateur lit.",
|
|
2359
2441
|
...context.grammarCard === null || context.grammarCard === void 0 ? [] : ["- Quand la rubrique apporte des op\xE9rations ou un \xE9cran que le g\xE9n\xE9rateur sait d\xE9river, \xE9cris la section `# G\xE9n\xE9ration` apr\xE8s `# @intent` et ses `# Comportements` dans la grammaire ferm\xE9e (carte ci-dessous) : le brouillon est jug\xE9 par le v\xE9rificateur du g\xE9n\xE9rateur avant d'\xEAtre montr\xE9. Quand elle ne se g\xE9n\xE8re pas, n'\xE9cris pas la section."],
|
|
2360
2442
|
"- Tu ne proposes jamais `status: validated` ni `implemented` : valider est le geste de l'humain.",
|
|
2443
|
+
"- HYPOTH\xC8SES : quand tu tranches un point que le brief ne dit pas (un nombre, un ordre, un p\xE9rim\xE8tre, un mot), ajoute apr\xE8s `# Budget` (et avant `# Design` si elle existe) une section `# Hypoth\xE8ses`, une puce par choix, en fran\xE7ais, courte ; sans hypoth\xE8se, pas de section.",
|
|
2444
|
+
'- QUESTIONS : quand la demande ci-dessous t\'y autorise et qu\'une r\xE9ponse du client CHANGERAIT la spec, tu peux, AU LIEU du fichier, r\xE9pondre par une ligne `QUESTIONS` seule puis un objet JSON `{"questions":[{"id":"kebab-case","text":"la question, en fran\xE7ais","options":["r\xE9ponse","r\xE9ponse"]}]}` \u2014 trois \xE0 cinq questions ferm\xE9es, deux \xE0 quatre options chacune, rien d\'autre avant ni apr\xE8s. Une question dont la r\xE9ponse ne changerait pas la spec ne se pose pas : elle devient une hypoth\xE8se.',
|
|
2361
2445
|
"- Quand la rubrique est un \xC9CRAN (une page du tableau de bord), ajoute apr\xE8s `# Budget` une section `# Design` qui commence par une ligne `Composition : <nom-kebab>` (le nom de la disposition, ex. `liste-d-abord`), puis l'ordre des vues, le pattern du catalogue de chacune (liste, formulaire, \xE9tat vide\u2026) et les \xE9tats mis en avant \u2014 jamais une couleur, jamais un composant invent\xE9.",
|
|
2362
2446
|
"",
|
|
2363
2447
|
"GABARIT DU REPO (\xE0 respecter \xE0 la lettre) :",
|
|
@@ -2379,7 +2463,10 @@ function buildBriefPrompt(context) {
|
|
|
2379
2463
|
...context.existingSpec === null ? [] : ["SPEC EXISTANTE :", context.existingSpec, ""],
|
|
2380
2464
|
"BRIEF :",
|
|
2381
2465
|
context.brief.trim(),
|
|
2382
|
-
...attachmentsPromptSection(context.attachments ?? [])
|
|
2466
|
+
...attachmentsPromptSection(context.attachments ?? []),
|
|
2467
|
+
...(context.answers ?? []).length === 0 ? [] : ["", "R\xC9PONSES DU CLIENT \xC0 TES QUESTIONS :", ...(context.answers ?? []).map((answer) => `- ${answer.questionId} : ${answer.answer}`)],
|
|
2468
|
+
"",
|
|
2469
|
+
context.questions?.allowed === true ? `Tourn\xE9e ${context.questions.round} sur ${MAX_QUESTION_ROUNDS} : si une r\xE9ponse du client changerait la spec, pose tes questions (format QUESTIONS) ; sinon, r\xE9dige le fichier.` : "N'interroge plus : r\xE9dige le fichier maintenant, et consigne ce que tu ne sais pas sous `# Hypoth\xE8ses`."
|
|
2383
2470
|
].join("\n");
|
|
2384
2471
|
return { system, user, documents: attachmentDocumentsOf(context.attachments ?? []) };
|
|
2385
2472
|
}
|
|
@@ -2471,7 +2558,7 @@ function judgeDraft(input) {
|
|
|
2471
2558
|
}
|
|
2472
2559
|
return { specId, problems: [...new Set(problems)] };
|
|
2473
2560
|
}
|
|
2474
|
-
async function
|
|
2561
|
+
async function runBrief(deps, input) {
|
|
2475
2562
|
if (input.specId !== void 0 && !FeatureNameSchema.safeParse(input.specId).success) {
|
|
2476
2563
|
throwOgygieError(
|
|
2477
2564
|
{
|
|
@@ -2502,6 +2589,8 @@ async function draftSpecFromBrief(deps, input) {
|
|
|
2502
2589
|
const knownDecisionIds = (await Promise.all(DECISION_FILES.map((file) => readIfPresent(deps.storage, file)))).flatMap((content) => parseKnownDecisionIds(content ?? ""));
|
|
2503
2590
|
const existingSpec = input.specId !== void 0 ? await readIfPresent(deps.storage, `specs/${input.specId}.spec.md`) : null;
|
|
2504
2591
|
const isAmendment = existingSpec !== null;
|
|
2592
|
+
const round = Math.min(Math.max(input.round ?? 1, 1), MAX_QUESTION_ROUNDS + 1);
|
|
2593
|
+
const questionsAllowed = input.skipQuestions !== true && round <= MAX_QUESTION_ROUNDS;
|
|
2505
2594
|
const prompt = buildBriefPrompt({
|
|
2506
2595
|
template,
|
|
2507
2596
|
productRules,
|
|
@@ -2512,15 +2601,40 @@ async function draftSpecFromBrief(deps, input) {
|
|
|
2512
2601
|
existingSpec,
|
|
2513
2602
|
specId: input.specId,
|
|
2514
2603
|
brief: input.brief,
|
|
2515
|
-
attachments: input.attachments
|
|
2604
|
+
attachments: input.attachments,
|
|
2605
|
+
answers: input.answers,
|
|
2606
|
+
questions: { allowed: questionsAllowed, round }
|
|
2516
2607
|
});
|
|
2517
2608
|
const usage = [];
|
|
2609
|
+
let refusedQuestions = false;
|
|
2518
2610
|
let previous;
|
|
2519
2611
|
let firstProblems = [];
|
|
2520
2612
|
let maxAttempts = MAX_ATTEMPTS;
|
|
2521
2613
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2614
|
+
deps.onStage?.({ stage: "drafting", attempt });
|
|
2522
2615
|
const answer = await deps.writer.write({ ...prompt, previous });
|
|
2523
2616
|
usage.push({ model: answer.model, ...answer.usage });
|
|
2617
|
+
const asked = extractQuestions(answer.text);
|
|
2618
|
+
if (asked !== null) {
|
|
2619
|
+
if ("problem" in asked) {
|
|
2620
|
+
deps.log(JSON.stringify({ action: "brief", attempt, questions: "malformed", ...answer.usage }));
|
|
2621
|
+
if (attempt === 1) firstProblems = [asked.problem];
|
|
2622
|
+
previous = { draft: answer.text, problems: [asked.problem] };
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
if (questionsAllowed) {
|
|
2626
|
+
deps.log(JSON.stringify({ action: "brief", attempt, round, questions: asked.questions.length, ...answer.usage }));
|
|
2627
|
+
return { kind: "questions", round, questions: asked.questions, usage };
|
|
2628
|
+
}
|
|
2629
|
+
deps.log(JSON.stringify({ action: "brief", attempt, round, questions: "refused", ...answer.usage }));
|
|
2630
|
+
if (!refusedQuestions) {
|
|
2631
|
+
refusedQuestions = true;
|
|
2632
|
+
maxAttempts += 1;
|
|
2633
|
+
}
|
|
2634
|
+
previous = { draft: answer.text, problems: [NO_MORE_QUESTIONS] };
|
|
2635
|
+
continue;
|
|
2636
|
+
}
|
|
2637
|
+
deps.onStage?.({ stage: "judging", attempt });
|
|
2524
2638
|
const content = extractSpecText(answer.text);
|
|
2525
2639
|
const judged = judgeDraft({
|
|
2526
2640
|
content,
|
|
@@ -2535,7 +2649,11 @@ async function draftSpecFromBrief(deps, input) {
|
|
|
2535
2649
|
deps.log(JSON.stringify({ action: "brief", attempt, specId: judged.specId, problems: judged.problems.length, generated: generationJudgement.tally !== null, ...answer.usage }));
|
|
2536
2650
|
if (judged.problems.length === 0 && judged.specId !== null) {
|
|
2537
2651
|
const specId = judged.specId;
|
|
2538
|
-
const
|
|
2652
|
+
const assumptions = assumptionsOfSpec(content);
|
|
2653
|
+
const briefForMessage = (input.brief.length > BRIEF_IN_MESSAGE_MAX ? `${input.brief.slice(0, BRIEF_IN_MESSAGE_MAX)} (\u2026)` : input.brief) + attachmentsProvenance(input.attachments ?? []) + (assumptions.length === 0 ? "" : `
|
|
2654
|
+
|
|
2655
|
+
Hypoth\xE8ses du mod\xE8le (D57) :
|
|
2656
|
+
${assumptions.map((line) => `- ${line}`).join("\n")}`);
|
|
2539
2657
|
const composition = isAmendment ? null : compositionOfSpec(content);
|
|
2540
2658
|
const compositions = [];
|
|
2541
2659
|
if (composition !== null) {
|
|
@@ -2553,7 +2671,9 @@ async function draftSpecFromBrief(deps, input) {
|
|
|
2553
2671
|
deps.log(JSON.stringify({ action: "composition", specId, outcome: thrown instanceof OgygieFailure ? thrown.error.code : "error" }));
|
|
2554
2672
|
}
|
|
2555
2673
|
const seen = /* @__PURE__ */ new Set([composition]);
|
|
2556
|
-
|
|
2674
|
+
const candidates = splitCompositions(answerText);
|
|
2675
|
+
deps.onStage?.({ stage: "compositions", composition: { done: 0, total: Math.min(candidates.length, MAX_COMPOSITIONS - 1) } });
|
|
2676
|
+
for (const candidate of candidates) {
|
|
2557
2677
|
if (seen.size >= MAX_COMPOSITIONS) break;
|
|
2558
2678
|
const name = compositionOfSpec(candidate);
|
|
2559
2679
|
const verdict = judgeDraft({ content: candidate, expectedId: specId, knownDecisionIds, takenIds: specIds, isAmendment: false });
|
|
@@ -2561,6 +2681,7 @@ async function draftSpecFromBrief(deps, input) {
|
|
|
2561
2681
|
deps.log(JSON.stringify({ action: "composition", specId, composition: name, problems: problems.length }));
|
|
2562
2682
|
if (problems.length > 0 || name === null) continue;
|
|
2563
2683
|
seen.add(name);
|
|
2684
|
+
deps.onStage?.({ stage: "compositions", composition: { done: seen.size - 1, total: Math.min(candidates.length, MAX_COMPOSITIONS - 1) } });
|
|
2564
2685
|
await deps.storage.write(`specs/${specId}.spec.md`, candidate, `spec: composition ${name} pour ${specId}, propos\xE9e par le mod\xE8le
|
|
2565
2686
|
|
|
2566
2687
|
Une autre disposition du m\xEAme \xE9cran ; la composition retenue par d\xE9faut suit dans le commit suivant.
|
|
@@ -2571,6 +2692,7 @@ ${briefForMessage}`);
|
|
|
2571
2692
|
}
|
|
2572
2693
|
compositions.unshift(composition);
|
|
2573
2694
|
}
|
|
2695
|
+
deps.onStage?.({ stage: "writing" });
|
|
2574
2696
|
await deps.storage.write(
|
|
2575
2697
|
`specs/${specId}.spec.md`,
|
|
2576
2698
|
content,
|
|
@@ -2580,14 +2702,18 @@ Brief :
|
|
|
2580
2702
|
${briefForMessage}`
|
|
2581
2703
|
);
|
|
2582
2704
|
return {
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2705
|
+
kind: "draft",
|
|
2706
|
+
result: {
|
|
2707
|
+
specId,
|
|
2708
|
+
attempts: attempt,
|
|
2709
|
+
problemsFixed: firstProblems,
|
|
2710
|
+
isNewFeature: !manifest.featureIds.has(specId),
|
|
2711
|
+
isAmendment,
|
|
2712
|
+
compositions,
|
|
2713
|
+
generation: generationJudgement.tally,
|
|
2714
|
+
assumptions,
|
|
2715
|
+
usage
|
|
2716
|
+
}
|
|
2591
2717
|
};
|
|
2592
2718
|
}
|
|
2593
2719
|
if (attempt === 1) firstProblems = judged.problems;
|
|
@@ -2606,37 +2732,144 @@ ${briefForMessage}`
|
|
|
2606
2732
|
);
|
|
2607
2733
|
}
|
|
2608
2734
|
|
|
2735
|
+
// src/server/hosted/brief-jobs.ts
|
|
2736
|
+
import { randomUUID } from "node:crypto";
|
|
2737
|
+
var BRIEF_BUDGET_MS = 9e4;
|
|
2738
|
+
var BRIEF_JOB_TTL_MS = 15 * 6e4;
|
|
2739
|
+
function createBriefJobs(deps) {
|
|
2740
|
+
const budgetMs = deps.budgetMs ?? BRIEF_BUDGET_MS;
|
|
2741
|
+
const ttlMs = deps.ttlMs ?? BRIEF_JOB_TTL_MS;
|
|
2742
|
+
const idOf = deps.idOf ?? randomUUID;
|
|
2743
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
2744
|
+
const settlements = /* @__PURE__ */ new Map();
|
|
2745
|
+
const sweep = () => {
|
|
2746
|
+
const now = deps.now();
|
|
2747
|
+
for (const [jobId, job] of jobs) {
|
|
2748
|
+
if (job.endedAt !== null && job.endedAt + ttlMs <= now) {
|
|
2749
|
+
jobs.delete(jobId);
|
|
2750
|
+
settlements.delete(jobId);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
const errorOf = (thrown) => thrown instanceof OgygieFailure ? thrown.error : ogygieError({
|
|
2755
|
+
code: "BRIEF_FAILED",
|
|
2756
|
+
message: "The brief failed before a draft or a round of questions came out.",
|
|
2757
|
+
file: null,
|
|
2758
|
+
cause: thrown instanceof Error ? thrown.message : String(thrown),
|
|
2759
|
+
suggestedFix: "Send the brief again; if it repeats, read the instance log."
|
|
2760
|
+
});
|
|
2761
|
+
return {
|
|
2762
|
+
start(owner, run) {
|
|
2763
|
+
sweep();
|
|
2764
|
+
const running = [...jobs.values()].find((job2) => job2.owner === owner && job2.endedAt === null);
|
|
2765
|
+
if (running !== void 0) {
|
|
2766
|
+
throwOgygieError(
|
|
2767
|
+
{
|
|
2768
|
+
code: "BRIEF_IN_PROGRESS",
|
|
2769
|
+
message: "A brief of yours is still running: one at a time.",
|
|
2770
|
+
file: null,
|
|
2771
|
+
cause: `job ${running.jobId} started ${deps.now() - running.startedAt} ms ago`,
|
|
2772
|
+
suggestedFix: "Wait for it to end (GET /api/briefs/<id>), then send the next brief.",
|
|
2773
|
+
details: { jobId: running.jobId }
|
|
2774
|
+
},
|
|
2775
|
+
409
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2778
|
+
const job = { jobId: idOf(), owner, startedAt: deps.now(), endedAt: null, progress: null, outcome: null, error: null };
|
|
2779
|
+
jobs.set(job.jobId, job);
|
|
2780
|
+
const settlement = run((progress) => {
|
|
2781
|
+
job.progress = progress;
|
|
2782
|
+
}).then((outcome) => {
|
|
2783
|
+
job.outcome = outcome;
|
|
2784
|
+
}).catch((thrown) => {
|
|
2785
|
+
job.error = errorOf(thrown);
|
|
2786
|
+
}).finally(() => {
|
|
2787
|
+
job.endedAt = deps.now();
|
|
2788
|
+
});
|
|
2789
|
+
settlements.set(job.jobId, settlement);
|
|
2790
|
+
return job.jobId;
|
|
2791
|
+
},
|
|
2792
|
+
read(jobId) {
|
|
2793
|
+
sweep();
|
|
2794
|
+
const job = jobs.get(jobId);
|
|
2795
|
+
if (job === void 0) {
|
|
2796
|
+
return throwOgygieError(
|
|
2797
|
+
{
|
|
2798
|
+
code: "BRIEF_JOB_LOST",
|
|
2799
|
+
message: "This brief job is unknown here: it expired, or the instance restarted since.",
|
|
2800
|
+
file: null,
|
|
2801
|
+
cause: `no job ${jobId} in memory`,
|
|
2802
|
+
suggestedFix: "Send the brief again; its text is still in your editor."
|
|
2803
|
+
},
|
|
2804
|
+
404
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
const base = { jobId: job.jobId, elapsedMs: Math.max(0, (job.endedAt ?? deps.now()) - job.startedAt), budgetMs };
|
|
2808
|
+
if (job.error !== null) return { ...base, stage: "failed", error: job.error };
|
|
2809
|
+
if (job.outcome !== null) {
|
|
2810
|
+
return job.outcome.kind === "draft" ? { ...base, stage: "done", result: job.outcome.result } : { ...base, stage: "questions", round: job.outcome.round, questions: job.outcome.questions };
|
|
2811
|
+
}
|
|
2812
|
+
const progress = job.progress ?? { stage: "drafting", attempt: 1 };
|
|
2813
|
+
switch (progress.stage) {
|
|
2814
|
+
case "drafting":
|
|
2815
|
+
case "judging":
|
|
2816
|
+
return { ...base, stage: progress.stage, attempt: progress.attempt };
|
|
2817
|
+
case "compositions":
|
|
2818
|
+
return { ...base, stage: "compositions", composition: progress.composition };
|
|
2819
|
+
case "writing":
|
|
2820
|
+
return { ...base, stage: "writing" };
|
|
2821
|
+
}
|
|
2822
|
+
},
|
|
2823
|
+
async settled(jobId) {
|
|
2824
|
+
await (settlements.get(jobId) ?? Promise.resolve());
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2609
2829
|
// src/server/hosted/brief-routes.ts
|
|
2610
2830
|
var BRIEF_BODY_LIMIT = 16 * 1024 * 1024;
|
|
2611
2831
|
async function registerBriefRoutes(app, deps) {
|
|
2612
2832
|
const route = app.withTypeProvider();
|
|
2833
|
+
const now = deps.now ?? Date.now;
|
|
2834
|
+
const jobs = deps.jobs ?? createBriefJobs({ now });
|
|
2613
2835
|
route.route({
|
|
2614
2836
|
method: "POST",
|
|
2615
2837
|
url: "/api/briefs",
|
|
2616
2838
|
bodyLimit: BRIEF_BODY_LIMIT,
|
|
2617
|
-
schema: { body: BriefInputSchema, response: {
|
|
2839
|
+
schema: { body: BriefInputSchema, response: { 202: BriefStartOutputSchema } },
|
|
2618
2840
|
handler: async (request, reply) => {
|
|
2619
2841
|
const user = hostedUserOf(request);
|
|
2620
2842
|
requireRole(user, "author", "write a brief");
|
|
2621
2843
|
const attachments = prepareBriefAttachments(request.body.attachments ?? []);
|
|
2622
2844
|
const storage = deps.storageFor(user);
|
|
2623
|
-
const
|
|
2624
|
-
|
|
2625
|
-
{
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2845
|
+
const { brief, specId, answers, round, skipQuestions } = request.body;
|
|
2846
|
+
const jobId = jobs.start(user.email, async (onStage) => {
|
|
2847
|
+
const outcome = await runBrief({ storage, writer: deps.writer, log: deps.log, onStage }, { brief, specId, attachments, answers, round, skipQuestions });
|
|
2848
|
+
if (outcome.kind !== "draft") return outcome;
|
|
2849
|
+
const proposal = "lastProposal" in storage ? storage.lastProposal : null;
|
|
2850
|
+
if (deps.usage !== void 0 && proposal !== null) {
|
|
2851
|
+
const at = new Date(now()).toISOString();
|
|
2852
|
+
try {
|
|
2853
|
+
await deps.usage.appendUsage(
|
|
2854
|
+
proposal.pullRequestNumber,
|
|
2855
|
+
outcome.result.usage.map((usage, index) => ({ at, action: "brief", attempt: index + 1, model: usage.model, inputTokens: usage.inputTokens, outputTokens: usage.outputTokens, actor: user.email }))
|
|
2856
|
+
);
|
|
2857
|
+
} catch (thrown) {
|
|
2858
|
+
deps.log(JSON.stringify({ action: "usage-journal", specId: outcome.result.specId, outcome: thrown instanceof Error ? thrown.message : String(thrown) }));
|
|
2859
|
+
}
|
|
2637
2860
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2861
|
+
return outcome;
|
|
2862
|
+
});
|
|
2863
|
+
return reply.code(202).send({ jobId });
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
route.route({
|
|
2867
|
+
method: "GET",
|
|
2868
|
+
url: "/api/briefs/:jobId",
|
|
2869
|
+
schema: { params: z10.object({ jobId: z10.string().min(1) }), response: { 200: BriefJobSchema } },
|
|
2870
|
+
handler: async (request, reply) => {
|
|
2871
|
+
hostedUserOf(request);
|
|
2872
|
+
return reply.code(200).send(jobs.read(request.params.jobId));
|
|
2640
2873
|
}
|
|
2641
2874
|
});
|
|
2642
2875
|
}
|
|
@@ -3147,17 +3380,17 @@ async function registerPromotionRoutes(app, deps) {
|
|
|
3147
3380
|
}
|
|
3148
3381
|
|
|
3149
3382
|
// src/server/hosted/usage.ts
|
|
3150
|
-
import { z as
|
|
3151
|
-
var UsageEntrySchema =
|
|
3152
|
-
at:
|
|
3383
|
+
import { z as z11 } from "zod";
|
|
3384
|
+
var UsageEntrySchema = z11.object({
|
|
3385
|
+
at: z11.string(),
|
|
3153
3386
|
/** brief → spec from the instance; `speculate` entries arrive with the generator's lot. */
|
|
3154
|
-
action:
|
|
3155
|
-
attempt:
|
|
3156
|
-
model:
|
|
3157
|
-
inputTokens:
|
|
3158
|
-
outputTokens:
|
|
3159
|
-
actor:
|
|
3160
|
-
specId:
|
|
3387
|
+
action: z11.enum(["brief", "speculate"]),
|
|
3388
|
+
attempt: z11.number().int().positive().optional(),
|
|
3389
|
+
model: z11.string(),
|
|
3390
|
+
inputTokens: z11.number().int().nonnegative(),
|
|
3391
|
+
outputTokens: z11.number().int().nonnegative(),
|
|
3392
|
+
actor: z11.string().optional(),
|
|
3393
|
+
specId: z11.string().optional()
|
|
3161
3394
|
});
|
|
3162
3395
|
var USAGE_MARKER = "<!-- ogygie-usage -->";
|
|
3163
3396
|
var DEFAULT_MODEL_COSTS = {
|
|
@@ -3192,7 +3425,7 @@ function parseUsageComment(body) {
|
|
|
3192
3425
|
const block = /```json\n([\s\S]*?)\n```/.exec(body)?.[1];
|
|
3193
3426
|
if (block === void 0) return [];
|
|
3194
3427
|
try {
|
|
3195
|
-
const parsed =
|
|
3428
|
+
const parsed = z11.array(UsageEntrySchema).safeParse(JSON.parse(block));
|
|
3196
3429
|
return parsed.success ? parsed.data : [];
|
|
3197
3430
|
} catch {
|
|
3198
3431
|
return [];
|
|
@@ -3267,9 +3500,9 @@ async function registerConsumptionRoutes(app, deps) {
|
|
|
3267
3500
|
}
|
|
3268
3501
|
|
|
3269
3502
|
// src/server/hosted/preview-routes.ts
|
|
3270
|
-
import { z as
|
|
3271
|
-
var PreviewParamsSchema = SpecParamsSchema.extend({ state:
|
|
3272
|
-
var CompositionPreviewParamsSchema = PreviewParamsSchema.extend({ composition:
|
|
3503
|
+
import { z as z12 } from "zod";
|
|
3504
|
+
var PreviewParamsSchema = SpecParamsSchema.extend({ state: z12.string().min(1).max(80) });
|
|
3505
|
+
var CompositionPreviewParamsSchema = PreviewParamsSchema.extend({ composition: z12.string().min(1).max(80) });
|
|
3273
3506
|
var PREVIEW_CSP = "sandbox; default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src data:";
|
|
3274
3507
|
async function registerPreviewRoutes(app, deps) {
|
|
3275
3508
|
const route = app.withTypeProvider();
|
|
@@ -3311,7 +3544,7 @@ async function registerPreviewRoutes(app, deps) {
|
|
|
3311
3544
|
}
|
|
3312
3545
|
|
|
3313
3546
|
// src/server/hosted/composition-routes.ts
|
|
3314
|
-
import { z as
|
|
3547
|
+
import { z as z13 } from "zod";
|
|
3315
3548
|
|
|
3316
3549
|
// src/server/hosted/compositions.ts
|
|
3317
3550
|
var COMPOSITION_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
@@ -3355,7 +3588,7 @@ La composition \xAB ${composition} \xBB est retenue parmi celles propos\xE9es ;
|
|
|
3355
3588
|
}
|
|
3356
3589
|
|
|
3357
3590
|
// src/server/hosted/composition-routes.ts
|
|
3358
|
-
var CompositionParamsSchema = SpecParamsSchema.extend({ composition:
|
|
3591
|
+
var CompositionParamsSchema = SpecParamsSchema.extend({ composition: z13.string().min(1).max(80) });
|
|
3359
3592
|
async function registerCompositionRoutes(app, deps) {
|
|
3360
3593
|
const route = app.withTypeProvider();
|
|
3361
3594
|
route.route({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:"Hanken Grotesk", "Helvetica Neue", Arial, sans-serif;--font-mono:"Fragment Mono", ui-monospace, "SF Mono", Menlo, monospace;--spacing:.25rem;--container-md:28rem;--container-2xl:42rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.8125rem;--text-sm--line-height:calc(1.25 / .875);--text-base:.9375rem;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.75rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:3rem;--text-3xl--line-height: 1.2 ;--text-4xl:4.5rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.2;--leading-normal:1.6;--leading-relaxed:1.625;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.5rem;--radius-lg:.75rem;--radius-xl:1.25rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--default-transition-duration:var(--duration-fast);--default-transition-timing-function:var(--ease-swell);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--font-display:"Cormorant Garamond", "Iowan Old Style", Georgia, serif;--radius-full:9999px;--spacing-1:.25rem;--spacing-2:.5rem;--spacing-3:.75rem;--spacing-4:1rem;--spacing-6:1.5rem;--spacing-8:2rem;--spacing-12:3rem;--spacing-16:4rem;--spacing-24:6rem;--ease-swell:cubic-bezier(.2, .7, .2, 1);--ease-retreat:cubic-bezier(.4, 0, .2, 1);--duration-fast:.16s;--duration-base:.32s;--duration-slow:.64s;--duration-count:.8s}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.top-0{top:0}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.-mx-8{margin-inline:calc(var(--spacing-8) * -1)}.mx-auto{margin-inline:auto}.-mt-8{margin-top:calc(var(--spacing-8) * -1)}.mt-1{margin-top:var(--spacing-1)}.mt-2{margin-top:var(--spacing-2)}.mt-3{margin-top:var(--spacing-3)}.mt-4{margin-top:var(--spacing-4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:var(--spacing-6)}.mt-auto{margin-top:auto}.mb-1{margin-bottom:var(--spacing-1)}.mb-3{margin-bottom:var(--spacing-3)}.mb-6{margin-bottom:var(--spacing-6)}.ml-1{margin-left:var(--spacing-1)}.ml-2{margin-left:var(--spacing-2)}.ml-3{margin-left:var(--spacing-3)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1{height:var(--spacing-1)}.h-2{height:var(--spacing-2)}.h-20{height:calc(var(--spacing) * 20)}.h-24{height:var(--spacing-24)}.h-40{height:calc(var(--spacing) * 40)}.h-64{height:calc(var(--spacing) * 64)}.h-96{height:calc(var(--spacing) * 96)}.h-\[480px\]{height:480px}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-2{width:var(--spacing-2)}.w-4{width:var(--spacing-4)}.w-16{width:var(--spacing-16)}.w-44{width:calc(var(--spacing) * 44)}.w-60{width:calc(var(--spacing) * 60)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.resize-y{resize:vertical}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing-1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:var(--spacing-2)}.gap-3{gap:var(--spacing-3)}.gap-4{gap:var(--spacing-4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing-1) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing-1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing-2) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing-2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing-3) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing-3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing-4) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing-4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing-6) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing-6) * calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-border>:not(:last-child)){border-color:var(--theme-border)}.self-center{align-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:var(--radius-full)}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-accent,.border-accent\/30{border-color:var(--theme-accent)}@supports (color:color-mix(in lab,red,red)){.border-accent\/30{border-color:color-mix(in oklab,var(--theme-accent) 30%,transparent)}}.border-border{border-color:var(--theme-border)}.border-border-strong,.border-border-strong\/60{border-color:var(--theme-border-strong)}@supports (color:color-mix(in lab,red,red)){.border-border-strong\/60{border-color:color-mix(in oklab,var(--theme-border-strong) 60%,transparent)}}.border-danger\/30{border-color:var(--theme-danger)}@supports (color:color-mix(in lab,red,red)){.border-danger\/30{border-color:color-mix(in oklab,var(--theme-danger) 30%,transparent)}}.border-danger\/40{border-color:var(--theme-danger)}@supports (color:color-mix(in lab,red,red)){.border-danger\/40{border-color:color-mix(in oklab,var(--theme-danger) 40%,transparent)}}.border-orichalcum\/40{border-color:var(--theme-orichalcum)}@supports (color:color-mix(in lab,red,red)){.border-orichalcum\/40{border-color:color-mix(in oklab,var(--theme-orichalcum) 40%,transparent)}}.border-positive\/30{border-color:var(--theme-positive)}@supports (color:color-mix(in lab,red,red)){.border-positive\/30{border-color:color-mix(in oklab,var(--theme-positive) 30%,transparent)}}.border-positive\/40{border-color:var(--theme-positive)}@supports (color:color-mix(in lab,red,red)){.border-positive\/40{border-color:color-mix(in oklab,var(--theme-positive) 40%,transparent)}}.border-warning\/30{border-color:var(--theme-warning)}@supports (color:color-mix(in lab,red,red)){.border-warning\/30{border-color:color-mix(in oklab,var(--theme-warning) 30%,transparent)}}.border-warning\/40{border-color:var(--theme-warning)}@supports (color:color-mix(in lab,red,red)){.border-warning\/40{border-color:color-mix(in oklab,var(--theme-warning) 40%,transparent)}}.border-warning\/50{border-color:var(--theme-warning)}@supports (color:color-mix(in lab,red,red)){.border-warning\/50{border-color:color-mix(in oklab,var(--theme-warning) 50%,transparent)}}.border-t-accent{border-top-color:var(--theme-accent)}.border-t-border-strong{border-top-color:var(--theme-border-strong)}.border-t-positive{border-top-color:var(--theme-positive)}.bg-accent{background-color:var(--theme-accent)}.bg-accent-soft{background-color:var(--theme-accent-soft)}.bg-border{background-color:var(--theme-border)}.bg-danger-soft,.bg-danger-soft\/40{background-color:var(--theme-danger-soft)}@supports (color:color-mix(in lab,red,red)){.bg-danger-soft\/40{background-color:color-mix(in oklab,var(--theme-danger-soft) 40%,transparent)}}.bg-positive-soft{background-color:var(--theme-positive-soft)}.bg-surface{background-color:var(--theme-surface)}.bg-surface-raised,.bg-surface-raised\/60{background-color:var(--theme-surface-raised)}@supports (color:color-mix(in lab,red,red)){.bg-surface-raised\/60{background-color:color-mix(in oklab,var(--theme-surface-raised) 60%,transparent)}}.bg-surface-sunken,.bg-surface-sunken\/50{background-color:var(--theme-surface-sunken)}@supports (color:color-mix(in lab,red,red)){.bg-surface-sunken\/50{background-color:color-mix(in oklab,var(--theme-surface-sunken) 50%,transparent)}}.bg-surface-sunken\/60{background-color:var(--theme-surface-sunken)}@supports (color:color-mix(in lab,red,red)){.bg-surface-sunken\/60{background-color:color-mix(in oklab,var(--theme-surface-sunken) 60%,transparent)}}.bg-surface-sunken\/70{background-color:var(--theme-surface-sunken)}@supports (color:color-mix(in lab,red,red)){.bg-surface-sunken\/70{background-color:color-mix(in oklab,var(--theme-surface-sunken) 70%,transparent)}}.bg-surface\/85{background-color:var(--theme-surface)}@supports (color:color-mix(in lab,red,red)){.bg-surface\/85{background-color:color-mix(in oklab,var(--theme-surface) 85%,transparent)}}.bg-warning{background-color:var(--theme-warning)}.bg-warning-soft{background-color:var(--theme-warning-soft)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing-1)}.p-3{padding:var(--spacing-3)}.p-4{padding:var(--spacing-4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:var(--spacing-6)}.px-1{padding-inline:var(--spacing-1)}.px-2{padding-inline:var(--spacing-2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:var(--spacing-3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:var(--spacing-4)}.px-6{padding-inline:var(--spacing-6)}.px-8{padding-inline:var(--spacing-8)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing-1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:var(--spacing-2)}.py-3{padding-block:var(--spacing-3)}.py-4{padding-block:var(--spacing-4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:var(--spacing-6)}.py-8{padding-block:var(--spacing-8)}.py-12{padding-block:var(--spacing-12)}.pt-4{padding-top:var(--spacing-4)}.pt-6{padding-top:var(--spacing-6)}.pt-12{padding-top:var(--spacing-12)}.pb-1{padding-bottom:var(--spacing-1)}.pb-2{padding-bottom:var(--spacing-2)}.pb-3{padding-bottom:var(--spacing-3)}.pb-4{padding-bottom:var(--spacing-4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-display{font-family:var(--font-display)}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.3em\]{--tw-tracking:.3em;letter-spacing:.3em}.tracking-\[0\.12em\]{--tw-tracking:.12em;letter-spacing:.12em}.tracking-\[0\.18em\]{--tw-tracking:.18em;letter-spacing:.18em}.tracking-\[0\.22em\]{--tw-tracking:.22em;letter-spacing:.22em}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-all{word-break:break-all}.whitespace-normal{white-space:normal}.text-accent{color:var(--theme-accent)}.text-accent-contrast{color:var(--theme-accent-contrast)}.text-danger{color:var(--theme-danger)}.text-orichalcum{color:var(--theme-orichalcum)}.text-positive{color:var(--theme-positive)}.text-text{color:var(--theme-text)}.text-text-faint{color:var(--theme-text-faint)}.text-text-muted{color:var(--theme-text-muted)}.text-warning{color:var(--theme-warning)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline-offset-4{text-underline-offset:4px}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-overlay{--tw-shadow:var(--theme-shadow-overlay);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-raised{--tw-shadow:var(--theme-shadow-raised);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[background-color\,border-color\,color\,transform\]{transition-property:background-color,border-color,color,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[border-color\,transform\,box-shadow\]{transition-property:border-color,transform,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-\(--duration-base\){--tw-duration:var(--duration-base);transition-duration:var(--duration-base)}.duration-\(--duration-fast\){--tw-duration:var(--duration-fast);transition-duration:var(--duration-fast)}.duration-\(--duration-slow\){--tw-duration:var(--duration-slow);transition-duration:var(--duration-slow)}.ease-swell{--tw-ease:var(--ease-swell);transition-timing-function:var(--ease-swell)}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.group-hover\:text-accent:is(:where(.group):hover *){color:var(--theme-accent)}}.marker\:text-accent ::marker{color:var(--theme-accent)}.marker\:text-accent::marker{color:var(--theme-accent)}.marker\:text-accent ::-webkit-details-marker{color:var(--theme-accent)}.marker\:text-accent::-webkit-details-marker{color:var(--theme-accent)}.marker\:text-orichalcum ::marker{color:var(--theme-orichalcum)}.marker\:text-orichalcum::marker{color:var(--theme-orichalcum)}.marker\:text-orichalcum ::-webkit-details-marker{color:var(--theme-orichalcum)}.marker\:text-orichalcum::-webkit-details-marker{color:var(--theme-orichalcum)}.file\:mr-3::file-selector-button{margin-right:var(--spacing-3)}.file\:rounded-md::file-selector-button{border-radius:var(--radius-md)}.file\:border::file-selector-button{border-style:var(--tw-border-style);border-width:1px}.file\:border-border::file-selector-button{border-color:var(--theme-border)}.file\:bg-surface::file-selector-button{background-color:var(--theme-surface)}.file\:px-3::file-selector-button{padding-inline:var(--spacing-3)}.file\:py-1\.5::file-selector-button{padding-block:calc(var(--spacing) * 1.5)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:text-text::file-selector-button{color:var(--theme-text)}.first\:mt-0:first-child{margin-top:0}@media(hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:border-accent:hover,.hover\:border-accent\/60:hover{border-color:var(--theme-accent)}@supports (color:color-mix(in lab,red,red)){.hover\:border-accent\/60:hover{border-color:color-mix(in oklab,var(--theme-accent) 60%,transparent)}}.hover\:border-border-strong:hover{border-color:var(--theme-border-strong)}.hover\:border-danger:hover{border-color:var(--theme-danger)}.hover\:bg-accent-strong:hover{background-color:var(--theme-accent-strong)}.hover\:bg-surface-sunken:hover,.hover\:bg-surface-sunken\/40:hover{background-color:var(--theme-surface-sunken)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-surface-sunken\/40:hover{background-color:color-mix(in oklab,var(--theme-surface-sunken) 40%,transparent)}}.hover\:text-accent:hover{color:var(--theme-accent)}.hover\:text-text:hover{color:var(--theme-text)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-overlay:hover{--tw-shadow:var(--theme-shadow-overlay);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:border-accent:focus{border-color:var(--theme-accent)}.active\:scale-\[0\.98\]:active{scale:.98}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:48rem){.md\:block{display:block}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-\[1fr_auto_1fr_auto_1fr\]{grid-template-columns:1fr auto 1fr auto 1fr}.md\:items-stretch{align-items:stretch}}@media(min-width:64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}.\[\&\.active\]\:bg-accent-soft.active{background-color:var(--theme-accent-soft)}.\[\&\.active\]\:font-medium.active{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.\[\&\.active\]\:text-accent.active{color:var(--theme-accent)}}:root{color-scheme:dark;--theme-surface:#0b1322;--theme-surface-raised:#131d30;--theme-surface-sunken:#060b15;--theme-border:#223049;--theme-border-strong:#5a6d93;--theme-text:#ebe6da;--theme-text-muted:#a2abba;--theme-text-faint:#7b8694;--theme-accent:#5cd6cc;--theme-accent-strong:#86e6de;--theme-accent-soft:#0e2b31;--theme-accent-contrast:#0b1322;--theme-accent-glow:#5cd6cc29;--theme-orichalcum:#d9a35a;--theme-positive:#8fd67a;--theme-positive-soft:#152a16;--theme-warning:#f2d06b;--theme-warning-soft:#2e2410;--theme-danger:#ff8a7a;--theme-danger-soft:#3a1612;--theme-focus:#5cd6cc;--theme-shadow-raised:inset 0 1px 0 #ffffff0d, 0 8px 24px #0006;--theme-shadow-overlay:inset 0 1px 0 #ffffff14, 0 24px 64px #000000a6;--theme-shadow-glow:0 0 0 1px #5cd6cc66, 0 0 48px #5cd6cc33}:root[data-theme=light]{color-scheme:light;--theme-surface:#f6f3ec;--theme-surface-raised:#fff;--theme-surface-sunken:#ece8de;--theme-border:#ddd7ca;--theme-border-strong:#8b8571;--theme-text:#121a2a;--theme-text-muted:#5a6475;--theme-text-faint:#667080;--theme-accent:#0f7580;--theme-accent-strong:#0a5c66;--theme-accent-soft:#dbf2f3;--theme-accent-contrast:#f3fbfb;--theme-accent-glow:#0f758024;--theme-orichalcum:#9e5420;--theme-positive:#2e7a1f;--theme-positive-soft:#e2f3dc;--theme-warning:#8a6400;--theme-warning-soft:#fbeccc;--theme-danger:#b42318;--theme-danger-soft:#fbe3df;--theme-focus:#0f7580;--theme-shadow-raised:0 1px 2px #121a2a14, 0 8px 24px #121a2a14;--theme-shadow-overlay:0 2px 4px #121a2a14, 0 24px 64px #121a2a29;--theme-shadow-glow:0 0 0 1px #0f758066, 0 0 48px #0f758024}body{background:var(--theme-surface);color:var(--theme-text);font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-normal);-webkit-font-smoothing:antialiased;margin:0}:where(a,button,input,select,textarea,[role=tab]):focus-visible{outline:2px solid var(--theme-focus);outline-offset:2px;border-radius:var(--radius-sm)}@media(prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}*{scrollbar-width:thin;scrollbar-color:var(--theme-border-strong) transparent}::placeholder{color:var(--theme-text-faint)}@keyframes studio-rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.studio-rise{animation:studio-rise var(--duration-base) var(--ease-swell) backwards}@keyframes studio-reveal{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}.studio-reveal{animation:studio-reveal var(--duration-slow) var(--ease-swell) backwards;animation-delay:calc(min(var(--i,0),7)*80ms)}@keyframes studio-breathe{0%,to{background-color:var(--theme-surface-sunken)}50%{background-color:var(--theme-surface-raised)}}.studio-breathe{animation:2.4s ease-in-out infinite studio-breathe}@keyframes studio-live{0%,to{opacity:.35}50%{opacity:1}}.studio-live{animation:2.4s ease-in-out infinite studio-live}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@keyframes pulse{50%{opacity:.5}}
|