mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-14 00:49:00 +02:00
fix(design-md): markdown edge cases: rule-opened legacy files, spaced fences, ~~~ blocks, duplicate headings, YAML 1.2 numerics
- insertMarker keyed on "starts with ---", so a legacy file opening with a horizontal rule got a `# gstack:` line rendered as a heading that the parser then never read back (the conversion question re-asked every run). It keys on parsed front matter. - A closing front-matter fence with trailing spaces (`--- `) made a valid spec file `unknown`; the closer is any whole `---` line. - `~~~` fences hid nothing, so a `## ` inside one was a section boundary and a splice corrupted the fence; both fence kinds are tracked and only the same kind closes an opener. - convertLegacy silently kept the first of two `## Layout` bodies (and one of `## Color` / `## Colors`); it refuses with DESIGN_MD_CONVERT_REFUSED and the bin leaves the file and writes no backup. - needsQuotes covers 0x / 0o / .inf / .nan (YAML 1.2 numerics that changed type on round-trip); emitYamlBlock throws on an object inside an array instead of writing "[object Object]". - The design binary coerces the model's extraction JSON at the parse boundary (null names, missing arrays) so the paid call's result survives. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
3994ddfdd7
commit
69b0856d42
+16
-5
@@ -84,7 +84,18 @@ Extract real values from what you see. Be specific about hex colors and font siz
|
||||
|
||||
const data = await response.json() as any;
|
||||
const content = data.choices?.[0]?.message?.content?.trim() || "";
|
||||
return JSON.parse(content) as ExtractedDesign;
|
||||
// The model's JSON is unvalidated: default the arrays and coerce the strings so a null name
|
||||
// cannot throw after the paid vision call.
|
||||
const raw = JSON.parse(content) as Partial<Record<keyof ExtractedDesign, unknown>>;
|
||||
const list = (v: unknown) => (Array.isArray(v) ? v : []);
|
||||
const str = (v: unknown) => (v === null || v === undefined ? "" : String(v));
|
||||
return {
|
||||
colors: list(raw.colors).map((c) => ({ name: str((c as Record<string, unknown>)?.name), hex: str((c as Record<string, unknown>)?.hex), usage: str((c as Record<string, unknown>)?.usage) })),
|
||||
typography: list(raw.typography).map((t) => ({ role: str((t as Record<string, unknown>)?.role), family: str((t as Record<string, unknown>)?.family), size: str((t as Record<string, unknown>)?.size), weight: str((t as Record<string, unknown>)?.weight) })),
|
||||
spacing: list(raw.spacing).map(str),
|
||||
layout: list(raw.layout).map(str),
|
||||
mood: str(raw.mood),
|
||||
};
|
||||
} catch (err: any) {
|
||||
console.error(`Design extraction error: ${err.message}`);
|
||||
return defaultDesign();
|
||||
@@ -138,13 +149,13 @@ export function updateDesignMd(
|
||||
const designPath = linkPath;
|
||||
|
||||
const colors: Record<string, string> = {};
|
||||
for (const c of extracted.colors) {
|
||||
const key = slug(c.name);
|
||||
for (const c of extracted.colors ?? []) {
|
||||
const key = slug(String(c.name ?? ""));
|
||||
if (key !== "token" && /^#[0-9a-fA-F]{3,8}$/.test(c.hex) && !(key in colors)) colors[key] = c.hex;
|
||||
}
|
||||
const typography: Record<string, Record<string, string>> = {};
|
||||
for (const t of extracted.typography) {
|
||||
const role = slug(t.role);
|
||||
for (const t of extracted.typography ?? []) {
|
||||
const role = slug(String(t.role ?? ""));
|
||||
if (role === "token" || typography[role]) continue;
|
||||
const entry: Record<string, string> = { fontFamily: t.family };
|
||||
if (t.size) entry.fontSize = t.size;
|
||||
|
||||
Reference in New Issue
Block a user