fix(spa): wildcard ref-graph keys by 8-hex UUID, not full slug id

Real cause behind the empty Syntax-column pills:
`buildWildcardGraph` keyed `outgoing` / `incoming` / `uuidToName` by
`mod.id` — the full slugged id like `wc_outfit_ea67b173`. But the
tokenizer's `@{8hex}` ref token captures only the 8-hex SUFFIX into
`meta.uuid` (e.g. `ea67b173`). The membership check
`knownUuids.has(targetUuid)` therefore never matched, so no edges
were ever added: hasInline lit up because it doesn't traverse the
graph, but `outgoing` / `incoming` stayed empty.

Switch the graph to key by the 8-hex short UUID extracted via
`extractModuleUuid(mod.id)` — same convention WildcardEditor's
`nameByUuid` already uses. Modules whose ids don't end in `_<8hex>`
(legacy imports, fixtures) are dropped: they can't be referenced
via `@{8hex}` anyway.

Update Wildcards.vue lookups in `syntaxView` + the "is-referenced"
filter to use `extractModuleUuid(row.id)` instead of `row.id`. Tests
for `buildWildcardGraph` updated to use realistic `<prefix>_<slug>_<8hex>`
ids; added a new case asserting modules without a hex suffix are skipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
DumiFlex
2026-04-29 01:31:08 +03:00
co-authored by Claude Opus 4.7
parent 12986454eb
commit 5f123722f0
3 changed files with 52 additions and 29 deletions
+23 -12
View File
@@ -88,10 +88,13 @@ describe("getWildcardSyntax", () => {
});
describe("buildWildcardGraph", () => {
// Module ids in the live system have the shape `<prefix>_<slug>_<8hex>` —
// `extractModuleUuid` peels off the 8-hex suffix to get the canonical
// identifier the `@{8hex}` ref token captures. These fixtures use the same
// shape so the graph behaves the way it does in production.
it("creates bidirectional UUID edges between two wildcards that reference each other", () => {
// IDs must be exactly 8 hex chars so tokenizeRich recognises @{uuid} refs.
const a = wc({ id: "aaaaaaaa", name: "alpha", var_binding: "alpha", options: [opt("see @{bbbbbbbb}")] });
const b = wc({ id: "bbbbbbbb", name: "beta", var_binding: "beta", options: [opt("see @{aaaaaaaa}")] });
const a = wc({ id: "wc_alpha_aaaaaaaa", name: "alpha", var_binding: "alpha", options: [opt("see @{bbbbbbbb}")] });
const b = wc({ id: "wc_beta_bbbbbbbb", name: "beta", var_binding: "beta", options: [opt("see @{aaaaaaaa}")] });
const g = buildWildcardGraph([a, b]);
expect(g.outgoing.get("aaaaaaaa")).toEqual(new Set(["bbbbbbbb"]));
expect(g.outgoing.get("bbbbbbbb")).toEqual(new Set(["aaaaaaaa"]));
@@ -100,26 +103,34 @@ describe("buildWildcardGraph", () => {
});
it("populates uuidToName from var_binding", () => {
const a = wc({ id: "aaaaaaaa", name: "alpha", var_binding: "alpha", options: [] });
const a = wc({ id: "wc_alpha_aaaaaaaa", name: "alpha", var_binding: "alpha", options: [] });
const g = buildWildcardGraph([a]);
expect(g.uuidToName.get("aaaaaaaa")).toBe("alpha");
});
it("yields empty edge sets for a wildcard with no references", () => {
const a = wc({ name: "lonely", var_binding: "lonely", options: [opt("just text")] });
const a = wc({ id: "wc_lonely_cccccccc", name: "lonely", var_binding: "lonely", options: [opt("just text")] });
const g = buildWildcardGraph([a]);
// Graph is keyed by mod.id; wc() defaults id to `wc_${name}`
expect(g.outgoing.get("wc_lonely")?.size ?? -1).toBe(0);
expect(g.incoming.get("wc_lonely")?.size ?? -1).toBe(0);
expect(g.outgoing.get("cccccccc")?.size ?? -1).toBe(0);
expect(g.incoming.get("cccccccc")?.size ?? -1).toBe(0);
});
it("drops dangling @{uuid} refs that don't resolve to a known wildcard", () => {
// ghostuuid is not in the module list — should be dropped.
const a = wc({ id: "aaaaaaaa", name: "alpha", var_binding: "alpha", options: [opt("@{ghostuuu} or @{bbbbbbbb}")] });
const b = wc({ id: "bbbbbbbb", name: "beta", var_binding: "beta", options: [opt("plain")] });
// `ddddddd1` is not in the module list — should be dropped.
const a = wc({ id: "wc_alpha_aaaaaaaa", name: "alpha", var_binding: "alpha", options: [opt("@{ddddddd1} or @{bbbbbbbb}")] });
const b = wc({ id: "wc_beta_bbbbbbbb", name: "beta", var_binding: "beta", options: [opt("plain")] });
const g = buildWildcardGraph([a, b]);
expect(g.outgoing.get("aaaaaaaa")).toEqual(new Set(["bbbbbbbb"]));
expect(g.incoming.get("ghostuuu")).toBeUndefined();
expect(g.incoming.get("ddddddd1")).toBeUndefined();
});
it("skips modules whose id has no 8-hex UUID suffix", () => {
// Legacy/fixture modules without a hex suffix can't be referenced via
// `@{8hex}` — they're silently dropped from the graph.
const a = wc({ id: "wc_legacy", name: "legacy", var_binding: "legacy", options: [] });
const g = buildWildcardGraph([a]);
expect(g.outgoing.size).toBe(0);
expect(g.uuidToName.size).toBe(0);
});
it("falls back to slug(name) when var_binding is missing", () => {
+18 -9
View File
@@ -7,13 +7,16 @@
* `docs/design-handoff/wildcardpipeline/project/data.jsx` so list-screen
* rendering matches the prototype exactly.
*
* Graph is keyed by module UUID (mod.id) — the stable identifier emitted by
* the tokenizer's `@{8hex}` ref syntax. `uuidToName` provides human-readable
* labels for display.
* Graph is keyed by the 8-hex short UUID — the SUFFIX of `mod.id` (e.g.
* `wc_outfit_ea67b173` → `ea67b173`). This is the same identifier the
* tokenizer's `@{8hex}` ref token captures into `meta.uuid`, and the same
* key WildcardEditor uses for `nameByUuid`. Keying by the full `mod.id`
* would miss every ref edge because targetUuid never matches the slugged
* source id.
*/
import type { ModuleRow, WildcardOption, WildcardPayload } from "../api/types";
import { tokenizeRich } from "./richTokenize";
import { toIdentifier } from "./slug";
import { extractModuleUuid, toIdentifier } from "./slug";
export interface SyntaxFlags {
/** Any option value contains an `@{uuid}` reference token. */
@@ -103,19 +106,25 @@ export function buildWildcardGraph(modules: ModuleRow[]): WildcardGraph {
const uuidToName = new Map<string, string>();
const wildcards = modules.filter((m) => m.type === "wildcard");
// Pair each wildcard with its 8-hex short UUID (extracted from the slug).
// Modules with non-standard ids (legacy imports, fixtures) are skipped —
// they can't be referenced via `@{8hex}` anyway.
const pairs: Array<{ mod: ModuleRow; uuid: string }> = [];
const knownUuids = new Set<string>();
for (const w of wildcards) {
knownUuids.add(w.id);
uuidToName.set(w.id, wildcardVarName(w));
const uuid = extractModuleUuid(w.id);
if (!uuid) continue;
pairs.push({ mod: w, uuid });
knownUuids.add(uuid);
uuidToName.set(uuid, wildcardVarName(w));
}
for (const w of wildcards) {
const srcUuid = w.id;
for (const { mod, uuid: srcUuid } of pairs) {
const out = new Set<string>();
outgoing.set(srcUuid, out);
for (const targetUuid of getWildcardSyntax(w).refTargets) {
for (const targetUuid of getWildcardSyntax(mod).refTargets) {
if (!knownUuids.has(targetUuid)) continue;
out.add(targetUuid);
let inSet = incoming.get(targetUuid);
+11 -8
View File
@@ -14,6 +14,7 @@ import {
buildWildcardGraph,
getWildcardSyntax,
} from "../utils/wildcardSyntax";
import { extractModuleUuid } from "../utils/slug";
const router = useRouter();
const store = useModuleStore();
@@ -48,13 +49,14 @@ interface SyntaxView {
}
function syntaxView(row: ModuleRow): SyntaxView {
// `buildWildcardGraph` keys the maps by `mod.id` (the 8-hex UUID — the same
// identifier captured by the tokenizer's `@{8hex}` ref token). Looking up
// by `wildcardVarName(row)` (a `$var` slug) always missed, which was why
// the Syntax column never lit up its ref/incoming pills. Use `row.id`.
// `buildWildcardGraph` keys outgoing/incoming by the 8-hex SHORT UUID
// (the suffix of `mod.id` — same identifier the tokenizer captures from
// `@{8hex}` refs). Looking up by `row.id` (full slug `wc_foo_ea67b173`)
// misses every entry. Use the extracted 8-hex form.
const sx = getWildcardSyntax(row);
const out = wildcardGraph.value.outgoing.get(row.id);
const inc = wildcardGraph.value.incoming.get(row.id);
const uuid = extractModuleUuid(row.id);
const out = uuid ? wildcardGraph.value.outgoing.get(uuid) : undefined;
const inc = uuid ? wildcardGraph.value.incoming.get(uuid) : undefined;
return {
hasInline: sx.hasInline,
outgoing: out ? [...out] : [],
@@ -77,8 +79,9 @@ const extraFilters = computed(() => [
key: "is-referenced",
label: "Referenced by others",
check: (m: ModuleRow) => {
// Graph is keyed by `mod.id` (UUID) — see `syntaxView` for context.
const inc = wildcardGraph.value.incoming.get(m.id);
// Graph is keyed by 8-hex short UUID — see `syntaxView` for context.
const uuid = extractModuleUuid(m.id);
const inc = uuid ? wildcardGraph.value.incoming.get(uuid) : undefined;
return !!inc && inc.size > 0;
},
},