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:
co-authored by
Claude Opus 4.7
parent
12986454eb
commit
5f123722f0
@@ -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", () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user