@rubytech/create-maxy-code 0.1.123 → 0.1.126
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/index.js +49 -4
- package/package.json +2 -2
- package/payload/platform/lib/graph-style/dist/index.d.ts +78 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/graph-style/dist/index.js +296 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -0
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +114 -0
- package/payload/platform/lib/graph-style/src/index.ts +307 -0
- package/payload/platform/lib/graph-style/tsconfig.json +9 -0
- package/payload/platform/lib/graph-style/vitest.config.ts +9 -0
- package/payload/platform/neo4j/schema.cypher +50 -0
- package/payload/platform/package-lock.json +318 -0
- package/payload/platform/package.json +3 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/docs/references/visitor-graph.md +5 -4
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +60 -0
- package/payload/platform/plugins/email/skills/email-composition/SKILL.md +6 -2
- package/payload/platform/plugins/graph-viewer/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/graph-viewer/PLUGIN.md +56 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts +7 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +74 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js +43 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts +28 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js +73 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/draw.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts +40 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js +77 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/render/layout.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts +45 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +170 -0
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -0
- package/payload/platform/plugins/graph-viewer/mcp/package.json +25 -0
- package/payload/platform/plugins/graph-viewer/mcp/vitest.config.ts +8 -0
- package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +35 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +17 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +11 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +12 -4
- package/payload/platform/plugins/venture-studio/skills/investor-data-room/SKILL.md +2 -0
- package/payload/platform/scripts/check-plugin-tools-mcp-consistency.mjs +136 -0
- package/payload/premium-plugins/venture-studio/skills/investor-data-room/SKILL.md +2 -0
- package/payload/server/public/assets/{admin-D6IfAzYY.js → admin-FcRHAL-3.js} +7 -7
- package/payload/server/public/assets/{data-BGbQyufe.js → data-Ds37mflX.js} +1 -1
- package/payload/server/public/assets/{graph-D-1lRTeL.js → graph-CmWRhaiS.js} +1 -1
- package/payload/server/public/assets/graph-labels-Ch2r00Gt.js +1 -0
- package/payload/server/public/assets/page-BOtNny_4.js +51 -0
- package/payload/server/public/assets/page-BcHhJXUt.js +1 -0
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/public/privacy.html +2 -2
- package/payload/server/server.js +236 -92
- package/payload/server/public/assets/graph-labels-BRXJHNYE.js +0 -1
- package/payload/server/public/assets/page-B4oirCvn.js +0 -1
- package/payload/server/public/assets/page-FmJ7PIYx.js +0 -51
package/dist/index.js
CHANGED
|
@@ -1171,7 +1171,17 @@ function installNeo4j() {
|
|
|
1171
1171
|
const neo4jDataDir = resolve(process.env.HOME ?? "/root", BRAND.configDir, "neo4j-data");
|
|
1172
1172
|
mkdirSync(neo4jDataDir, { recursive: true });
|
|
1173
1173
|
shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { redact: true });
|
|
1174
|
-
|
|
1174
|
+
// Start Neo4j under brew's launchd supervision. Linux path starts the
|
|
1175
|
+
// service via systemd a few lines below; without this start the darwin
|
|
1176
|
+
// brand server binds successfully but every Cypher write fails with
|
|
1177
|
+
// "Failed to connect to server" because nothing is listening on 7687.
|
|
1178
|
+
// `brew services start` is idempotent — if already running it no-ops,
|
|
1179
|
+
// and the LaunchAgent persists across reboots.
|
|
1180
|
+
const brewStart = spawnSync("brew", ["services", "start", "neo4j"], { stdio: "inherit" });
|
|
1181
|
+
if (brewStart.status !== 0) {
|
|
1182
|
+
throw new Error(`brew services start neo4j failed (exit ${brewStart.status}) — Neo4j must be running before the brand server starts or all graph writes will fail`);
|
|
1183
|
+
}
|
|
1184
|
+
console.log(" Neo4j installed and started via Homebrew launchd. Password stored securely.");
|
|
1175
1185
|
return;
|
|
1176
1186
|
}
|
|
1177
1187
|
// Neo4j 5.x supports Java 17 and 21. Debian Bookworm ships 17, Trixie ships 21.
|
|
@@ -2562,6 +2572,17 @@ function installServiceDarwin() {
|
|
|
2562
2572
|
// must be written here or the server hard-fails before binding the listener.
|
|
2563
2573
|
// VNC/X11 vars (DISPLAY, RFB_PORT, WEBSOCKIFY_PORT, CDP_PORT) are Pi-only
|
|
2564
2574
|
// and excluded — darwin runs no kiosk display stack.
|
|
2575
|
+
//
|
|
2576
|
+
// Darwin collapses the two-process model: Linux runs a separate
|
|
2577
|
+
// `maxy-edge.service` on 0.0.0.0:PORT that reverse-proxies into the brand
|
|
2578
|
+
// server on 127.0.0.1:MAXY_UI_INTERNAL_PORT, with edge surviving brand
|
|
2579
|
+
// restarts so the browser WebSocket stays connected. Edge also owns the
|
|
2580
|
+
// VNC websockify stack, which darwin doesn't have. On darwin the brand
|
|
2581
|
+
// server binds the public port directly:
|
|
2582
|
+
// HOSTNAME=0.0.0.0 → bind all interfaces (LAN reachable)
|
|
2583
|
+
// MAXY_UI_INTERNAL_PORT=PORT → same port, no +1 split
|
|
2584
|
+
// Tradeoff: no zero-downtime restarts; a brand restart drops live
|
|
2585
|
+
// websockets. Acceptable on a single-operator dev Mac.
|
|
2565
2586
|
const installAccountId = resolveInstallAccountId();
|
|
2566
2587
|
if (!installAccountId) {
|
|
2567
2588
|
throw new Error(`installServiceDarwin: no account discovered at ${INSTALL_DIR}/data/accounts/<uuid>/account.json — ` +
|
|
@@ -2582,11 +2603,11 @@ function installServiceDarwin() {
|
|
|
2582
2603
|
["EMBED_DIMENSIONS", String(EMBED_DIMS)],
|
|
2583
2604
|
["NEO4J_URI", `bolt://localhost:${NEO4J_PORT}`],
|
|
2584
2605
|
["PORT", String(PORT)],
|
|
2585
|
-
["MAXY_UI_INTERNAL_PORT", String(PORT
|
|
2606
|
+
["MAXY_UI_INTERNAL_PORT", String(PORT)],
|
|
2586
2607
|
["ACCOUNT_ID", installAccountId],
|
|
2587
2608
|
["MAXY_PLATFORM_ROOT", `${INSTALL_DIR}/platform`],
|
|
2588
2609
|
["CLAUDE_CONFIG_DIR", `${persistDir}/.claude`],
|
|
2589
|
-
["HOSTNAME", "
|
|
2610
|
+
["HOSTNAME", "0.0.0.0"],
|
|
2590
2611
|
["KEEP_ALIVE_TIMEOUT", "61000"],
|
|
2591
2612
|
]) {
|
|
2592
2613
|
const re = new RegExp(`^${key}=.*$`, "m");
|
|
@@ -2598,7 +2619,7 @@ function installServiceDarwin() {
|
|
|
2598
2619
|
}
|
|
2599
2620
|
}
|
|
2600
2621
|
writeFileSync(envPath, envContent);
|
|
2601
|
-
logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}, MAXY_UI_INTERNAL_PORT=${PORT
|
|
2622
|
+
logFile(` .env: DISPLAY_MODE=${DISPLAY_MODE}, EMBED_MODEL=${EMBED_MODEL}, EMBED_DIMENSIONS=${EMBED_DIMS}, NEO4J_URI=bolt://localhost:${NEO4J_PORT}, PORT=${PORT}, MAXY_UI_INTERNAL_PORT=${PORT} (darwin-collapsed), HOSTNAME=0.0.0.0, ACCOUNT_ID=${installAccountId.slice(0, 8)}…, CLAUDE_CONFIG_DIR=${persistDir}/.claude`);
|
|
2602
2623
|
}
|
|
2603
2624
|
catch (err) {
|
|
2604
2625
|
console.error(` WARNING: failed to write .env to ${envPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3169,6 +3190,30 @@ WantedBy=multi-user.target
|
|
|
3169
3190
|
spawnSync("systemctl", ["--user", "restart", edgeUnitShort], { stdio: "inherit" });
|
|
3170
3191
|
spawnSync("systemctl", ["--user", "restart", claudeSessionManagerUnitShort], { stdio: "inherit" });
|
|
3171
3192
|
spawnSync("systemctl", ["--user", "restart", unitName], { stdio: "inherit" });
|
|
3193
|
+
// Session manager runs in its own systemd user unit and can boot-fail silently
|
|
3194
|
+
// (e.g. mcp-block-missing). The web server below comes up regardless, so the
|
|
3195
|
+
// install terminal would otherwise report success while every API call 503s.
|
|
3196
|
+
// Surface the boot failure on the trusted surface and refuse to declare success.
|
|
3197
|
+
console.log(" Waiting for session manager...");
|
|
3198
|
+
let sessionManagerActive = false;
|
|
3199
|
+
for (let i = 0; i < 5; i++) {
|
|
3200
|
+
const probe = spawnSync("systemctl", ["--user", "is-active", claudeSessionManagerUnitShort], { stdio: "pipe", encoding: "utf-8" });
|
|
3201
|
+
if ((probe.stdout ?? "").trim() === "active") {
|
|
3202
|
+
sessionManagerActive = true;
|
|
3203
|
+
break;
|
|
3204
|
+
}
|
|
3205
|
+
spawnSync("sleep", ["2"]);
|
|
3206
|
+
}
|
|
3207
|
+
if (!sessionManagerActive) {
|
|
3208
|
+
const status = spawnSync("systemctl", ["--user", "status", claudeSessionManagerUnitShort, "--no-pager", "-n", "20"], { stdio: "pipe", encoding: "utf-8" });
|
|
3209
|
+
const statusOutput = `${status.stdout ?? ""}${status.stderr ?? ""}`.trimEnd();
|
|
3210
|
+
console.error(statusOutput);
|
|
3211
|
+
logFile(statusOutput);
|
|
3212
|
+
const failureLine = `Session manager (${claudeSessionManagerUnitShort}) failed to start. See above for boot-failed reason.`;
|
|
3213
|
+
console.error(failureLine);
|
|
3214
|
+
logFile(failureLine);
|
|
3215
|
+
process.exit(1);
|
|
3216
|
+
}
|
|
3172
3217
|
// Wait for the server to come up
|
|
3173
3218
|
console.log(" Waiting for web server...");
|
|
3174
3219
|
let webServerUp = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rubytech/create-maxy-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.126",
|
|
4
4
|
"description": "Install Maxy — AI for Productive People",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-maxy-code": "./dist/index.js"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build": "tsc",
|
|
14
14
|
"bundle": "node scripts/bundle.js",
|
|
15
15
|
"test": "npm run build && node --test 'dist/__tests__/*.test.js'",
|
|
16
|
-
"prepublishOnly": "bash ../../platform/scripts/verify-skill-tool-surface.sh && node ../../platform/scripts/check-no-task-id-leaks.mjs && node ../../platform/scripts/check-no-esm-require.mjs && node ../../platform/scripts/check-skill-load-coverage.mjs && node ../../platform/ui/scripts/check-route-wiring.mjs && node ../../platform/ui/scripts/check-edge-admin-routes.mjs && npm run build && node --test 'dist/__tests__/*.test.js' && chmod +x dist/index.js && npm run bundle && bash ../../platform/scripts/smoke-boot-services.sh && node ../../platform/ui/scripts/check-bundle-node-imports.mjs --dir=./payload/server/public/assets"
|
|
16
|
+
"prepublishOnly": "bash ../../platform/scripts/verify-skill-tool-surface.sh && node ../../platform/scripts/check-plugin-tools-mcp-consistency.mjs && node ../../platform/scripts/check-no-task-id-leaks.mjs && node ../../platform/scripts/check-no-esm-require.mjs && node ../../platform/scripts/check-skill-load-coverage.mjs && node ../../platform/ui/scripts/check-route-wiring.mjs && node ../../platform/ui/scripts/check-edge-admin-routes.mjs && npm run build && node --test 'dist/__tests__/*.test.js' && chmod +x dist/index.js && npm run bundle && bash ../../platform/scripts/smoke-boot-services.sh && node ../../platform/ui/scripts/check-bundle-node-imports.mjs --dir=./payload/server/public/assets"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared palette + display-string primitives for any surface that renders
|
|
3
|
+
* the Neo4j graph. Both the live `/graph` page in platform/ui and the
|
|
4
|
+
* graph-viewer MCP plugin's PNG renderer import from here so a node looks
|
|
5
|
+
* the same wherever it appears: same colour family per label, same short
|
|
6
|
+
* canvas label per node.
|
|
7
|
+
*
|
|
8
|
+
* UI-only constants (filter-popover sets, zoom-tier wrap helpers,
|
|
9
|
+
* agent-action label sets) stay in platform/ui/app/lib/graph-labels.ts and
|
|
10
|
+
* platform/ui/app/graph/display-helpers.ts — they re-export the symbols
|
|
11
|
+
* defined here. The two parity tests (this lib's __tests__ and the UI's
|
|
12
|
+
* unchanged colour-stability tests) keep the surfaces in lockstep.
|
|
13
|
+
*
|
|
14
|
+
* Sources of truth:
|
|
15
|
+
* - Label list: .docs/neo4j.md "Schema (Phase 0)" + "Standards Compliance"
|
|
16
|
+
* - Brand colour for LocalBusiness: brand.json defaultColors.primary
|
|
17
|
+
* (resolved at runtime; this module supplies the schema-default fallback)
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Label → display colour mapping. Closed list of node labels written by
|
|
21
|
+
* the Maxy MCP write paths. A new label without an entry here renders in
|
|
22
|
+
* FALLBACK_LABEL_COLOUR and is omitted from the legend.
|
|
23
|
+
*/
|
|
24
|
+
export declare const GRAPH_LABEL_COLOURS: Record<string, string>;
|
|
25
|
+
export declare const FALLBACK_LABEL_COLOUR = "#94A3B8";
|
|
26
|
+
export declare const ALL_GRAPH_LABELS: readonly string[];
|
|
27
|
+
/**
|
|
28
|
+
* Select the sublabel whose colour should drive the node hue. Walks the
|
|
29
|
+
* label array from index 1 (skipping the base label at index 0) and
|
|
30
|
+
* returns the first sublabel that has a registered colour; falls back to
|
|
31
|
+
* labels[0]. Pure — drift-counting lives in resolveNodeColour.
|
|
32
|
+
*/
|
|
33
|
+
export declare function pickDisplayLabel(labels: readonly string[]): string | null;
|
|
34
|
+
export declare function colourForLabel(label: string): string;
|
|
35
|
+
export interface ResolvedNodeColour {
|
|
36
|
+
displayLabel: string | null;
|
|
37
|
+
colour: string;
|
|
38
|
+
driftCandidates: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Single-source colour resolver. driftCandidates counts registered
|
|
42
|
+
* sublabels in labels[1..]; a node with two registered sublabels has
|
|
43
|
+
* driftCandidates = 2 and its rendered hue is order-dependent.
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveNodeColour(labels: readonly string[]): ResolvedNodeColour;
|
|
46
|
+
/**
|
|
47
|
+
* Structural type used by display-string helpers. Mirrors the shape of
|
|
48
|
+
* GraphNode without coupling to it.
|
|
49
|
+
*/
|
|
50
|
+
export interface GraphNodeLike {
|
|
51
|
+
id: string;
|
|
52
|
+
labels: string[];
|
|
53
|
+
properties: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* YYYY-MM-DD HH:mm from an ISO 8601 prefix. Reads the prefix directly so
|
|
57
|
+
* timezone-stored UTC timestamps render identically across operator
|
|
58
|
+
* timezones; parsing to Date + reformatting would flip the day.
|
|
59
|
+
*/
|
|
60
|
+
export declare function formatRunTimestamp(iso: string): string | null;
|
|
61
|
+
/** HH:mm:ss from an ISO 8601 prefix. Same tz-stable contract. */
|
|
62
|
+
export declare function formatMessageTimestamp(iso: string): string | null;
|
|
63
|
+
/**
|
|
64
|
+
* 24-char canvas label for a node. Label precedence per label family
|
|
65
|
+
* (ToolCall.toolName, WorkflowRun startedAt formatted, WorkflowStep.label,
|
|
66
|
+
* Person givenName+familyName compose, Agent displayName/slug, Message
|
|
67
|
+
* role+timestamp, ConversationArchive.title), then a generic fallthrough
|
|
68
|
+
* across curated keys (name, title, summary, givenName, subject, text),
|
|
69
|
+
* then a Conversation-specific `Conv <id8>` fallback so unpopulated
|
|
70
|
+
* conversations don't render as the literal label.
|
|
71
|
+
*/
|
|
72
|
+
export declare function pickShortLabel(node: GraphNodeLike): string;
|
|
73
|
+
/**
|
|
74
|
+
* Untruncated sibling of pickShortLabel. Used by side-panel headings and
|
|
75
|
+
* tooltips where the 24-char canvas-overlap cap does not apply.
|
|
76
|
+
*/
|
|
77
|
+
export declare function pickDisplayName(node: GraphNodeLike): string;
|
|
78
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA4EtD,CAAA;AAED,eAAO,MAAM,qBAAqB,YAAY,CAAA;AAE9C,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAE7C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAOzE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,kBAAkB,CAU/E;AASD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAKD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAgE1D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAuC3D"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared palette + display-string primitives for any surface that renders
|
|
4
|
+
* the Neo4j graph. Both the live `/graph` page in platform/ui and the
|
|
5
|
+
* graph-viewer MCP plugin's PNG renderer import from here so a node looks
|
|
6
|
+
* the same wherever it appears: same colour family per label, same short
|
|
7
|
+
* canvas label per node.
|
|
8
|
+
*
|
|
9
|
+
* UI-only constants (filter-popover sets, zoom-tier wrap helpers,
|
|
10
|
+
* agent-action label sets) stay in platform/ui/app/lib/graph-labels.ts and
|
|
11
|
+
* platform/ui/app/graph/display-helpers.ts — they re-export the symbols
|
|
12
|
+
* defined here. The two parity tests (this lib's __tests__ and the UI's
|
|
13
|
+
* unchanged colour-stability tests) keep the surfaces in lockstep.
|
|
14
|
+
*
|
|
15
|
+
* Sources of truth:
|
|
16
|
+
* - Label list: .docs/neo4j.md "Schema (Phase 0)" + "Standards Compliance"
|
|
17
|
+
* - Brand colour for LocalBusiness: brand.json defaultColors.primary
|
|
18
|
+
* (resolved at runtime; this module supplies the schema-default fallback)
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.ALL_GRAPH_LABELS = exports.FALLBACK_LABEL_COLOUR = exports.GRAPH_LABEL_COLOURS = void 0;
|
|
22
|
+
exports.pickDisplayLabel = pickDisplayLabel;
|
|
23
|
+
exports.colourForLabel = colourForLabel;
|
|
24
|
+
exports.resolveNodeColour = resolveNodeColour;
|
|
25
|
+
exports.formatRunTimestamp = formatRunTimestamp;
|
|
26
|
+
exports.formatMessageTimestamp = formatMessageTimestamp;
|
|
27
|
+
exports.pickShortLabel = pickShortLabel;
|
|
28
|
+
exports.pickDisplayName = pickDisplayName;
|
|
29
|
+
/**
|
|
30
|
+
* Label → display colour mapping. Closed list of node labels written by
|
|
31
|
+
* the Maxy MCP write paths. A new label without an entry here renders in
|
|
32
|
+
* FALLBACK_LABEL_COLOUR and is omitted from the legend.
|
|
33
|
+
*/
|
|
34
|
+
exports.GRAPH_LABEL_COLOURS = {
|
|
35
|
+
// Business identity — slate-blue family
|
|
36
|
+
LocalBusiness: '#4F6B8A',
|
|
37
|
+
Service: '#6B85A0',
|
|
38
|
+
PriceSpecification: '#8AA0B8',
|
|
39
|
+
OpeningHoursSpecification: '#A8BACE',
|
|
40
|
+
// Estate-agent ontology. Listing/Property share the business slate-blue
|
|
41
|
+
// family; Viewing/Offer are transactional events placed in a warm amber
|
|
42
|
+
// family distinct from Agent and from the dusty-rose Task/Project/Event
|
|
43
|
+
// hues so the legend reads them as a separate band.
|
|
44
|
+
Listing: '#3F5670',
|
|
45
|
+
Property: '#5C7A99',
|
|
46
|
+
Viewing: '#D4A574',
|
|
47
|
+
Offer: '#B08850',
|
|
48
|
+
// External business entity — schema:Organization, written by memory-write
|
|
49
|
+
// as the Supplier label. Warm tobacco — distinct hue from internal-business
|
|
50
|
+
// slates so external entities are scannable at a glance.
|
|
51
|
+
Organization: '#8A6B47',
|
|
52
|
+
// People — terracotta family (warm, human)
|
|
53
|
+
Person: '#B86E4A',
|
|
54
|
+
UserProfile: '#D08960',
|
|
55
|
+
Preference: '#DFA67A',
|
|
56
|
+
AdminUser: '#8C5230',
|
|
57
|
+
AccessGrant: '#66381F',
|
|
58
|
+
// Knowledge — moss family (organic)
|
|
59
|
+
KnowledgeDocument: '#6E8A5A',
|
|
60
|
+
// chunked WhatsApp/messaging archive parent. Sits in the
|
|
61
|
+
// knowledge moss family because it's structurally a document parent
|
|
62
|
+
// (HAS_SECTION → :Section:Conversation chunks); the conversational
|
|
63
|
+
// plum family is reserved for the live Conversation/Message labels.
|
|
64
|
+
ConversationArchive: '#82A06A',
|
|
65
|
+
Section: '#8AA876',
|
|
66
|
+
Chunk: '#A6C194',
|
|
67
|
+
DigitalDocument: '#5A7548',
|
|
68
|
+
CreativeWork: '#B5C9A2',
|
|
69
|
+
Question: '#C7D6B5',
|
|
70
|
+
FAQPage: '#95B385',
|
|
71
|
+
DefinedTerm: '#76906A',
|
|
72
|
+
Review: '#3F5A2E',
|
|
73
|
+
ImageObject: '#A0BB8C',
|
|
74
|
+
// Conversational — muted plum/lavender family.
|
|
75
|
+
// Pairwise-distinctness invariants enforced by the UI test
|
|
76
|
+
// graph-labels.test.ts; UserMessage matches Preference (Person/family cue,
|
|
77
|
+
// the one intentional duplicate in the registry).
|
|
78
|
+
Conversation: '#6B5A85',
|
|
79
|
+
AdminConversation: '#4F4070',
|
|
80
|
+
PublicConversation: '#8A75A8',
|
|
81
|
+
Message: '#6B7280',
|
|
82
|
+
UserMessage: '#DFA67A',
|
|
83
|
+
AssistantMessage: '#C9A876',
|
|
84
|
+
ToolCall: '#B5ABCB',
|
|
85
|
+
// Tasks / projects / events — dusty rose family
|
|
86
|
+
Task: '#B0617A',
|
|
87
|
+
Project: '#8E4A60',
|
|
88
|
+
Event: '#C68095',
|
|
89
|
+
// Workflows — muted teal
|
|
90
|
+
Workflow: '#3A6F77',
|
|
91
|
+
WorkflowStep: '#5A8E96',
|
|
92
|
+
WorkflowRun: '#7AAAB2',
|
|
93
|
+
StepResult: '#9CC4CB',
|
|
94
|
+
// Email — muted moss, hue-adjacent to Knowledge but lower-saturation
|
|
95
|
+
// and warmer; never co-rendered with KnowledgeDocument.
|
|
96
|
+
Email: '#6F7F4A',
|
|
97
|
+
EmailAccount: '#91A063',
|
|
98
|
+
// Public-agent projection — burnished bronze sits between
|
|
99
|
+
// people-terracotta and email-moss but shares neither hue.
|
|
100
|
+
Agent: '#B8893D',
|
|
101
|
+
};
|
|
102
|
+
exports.FALLBACK_LABEL_COLOUR = '#94A3B8';
|
|
103
|
+
exports.ALL_GRAPH_LABELS = Object.freeze(Object.keys(exports.GRAPH_LABEL_COLOURS));
|
|
104
|
+
/**
|
|
105
|
+
* Select the sublabel whose colour should drive the node hue. Walks the
|
|
106
|
+
* label array from index 1 (skipping the base label at index 0) and
|
|
107
|
+
* returns the first sublabel that has a registered colour; falls back to
|
|
108
|
+
* labels[0]. Pure — drift-counting lives in resolveNodeColour.
|
|
109
|
+
*/
|
|
110
|
+
function pickDisplayLabel(labels) {
|
|
111
|
+
for (let i = 1; i < labels.length; i++) {
|
|
112
|
+
if (Object.prototype.hasOwnProperty.call(exports.GRAPH_LABEL_COLOURS, labels[i])) {
|
|
113
|
+
return labels[i];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return labels[0] ?? null;
|
|
117
|
+
}
|
|
118
|
+
function colourForLabel(label) {
|
|
119
|
+
return exports.GRAPH_LABEL_COLOURS[label] ?? exports.FALLBACK_LABEL_COLOUR;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Single-source colour resolver. driftCandidates counts registered
|
|
123
|
+
* sublabels in labels[1..]; a node with two registered sublabels has
|
|
124
|
+
* driftCandidates = 2 and its rendered hue is order-dependent.
|
|
125
|
+
*/
|
|
126
|
+
function resolveNodeColour(labels) {
|
|
127
|
+
const displayLabel = pickDisplayLabel(labels);
|
|
128
|
+
const colour = displayLabel ? colourForLabel(displayLabel) : exports.FALLBACK_LABEL_COLOUR;
|
|
129
|
+
let driftCandidates = 0;
|
|
130
|
+
for (let i = 1; i < labels.length; i++) {
|
|
131
|
+
if (Object.prototype.hasOwnProperty.call(exports.GRAPH_LABEL_COLOURS, labels[i])) {
|
|
132
|
+
driftCandidates++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { displayLabel, colour, driftCandidates };
|
|
136
|
+
}
|
|
137
|
+
const ISO_MINUTE_RE = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/;
|
|
138
|
+
const ISO_SECOND_RE = /^\d{4}-\d{2}-\d{2}T(\d{2}:\d{2}:\d{2})/;
|
|
139
|
+
/**
|
|
140
|
+
* YYYY-MM-DD HH:mm from an ISO 8601 prefix. Reads the prefix directly so
|
|
141
|
+
* timezone-stored UTC timestamps render identically across operator
|
|
142
|
+
* timezones; parsing to Date + reformatting would flip the day.
|
|
143
|
+
*/
|
|
144
|
+
function formatRunTimestamp(iso) {
|
|
145
|
+
const m = iso.match(ISO_MINUTE_RE);
|
|
146
|
+
if (!m)
|
|
147
|
+
return null;
|
|
148
|
+
return `${m[1]} ${m[2]}`;
|
|
149
|
+
}
|
|
150
|
+
/** HH:mm:ss from an ISO 8601 prefix. Same tz-stable contract. */
|
|
151
|
+
function formatMessageTimestamp(iso) {
|
|
152
|
+
const m = iso.match(ISO_SECOND_RE);
|
|
153
|
+
if (!m)
|
|
154
|
+
return null;
|
|
155
|
+
return m[1];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 24-char canvas label for a node. Label precedence per label family
|
|
159
|
+
* (ToolCall.toolName, WorkflowRun startedAt formatted, WorkflowStep.label,
|
|
160
|
+
* Person givenName+familyName compose, Agent displayName/slug, Message
|
|
161
|
+
* role+timestamp, ConversationArchive.title), then a generic fallthrough
|
|
162
|
+
* across curated keys (name, title, summary, givenName, subject, text),
|
|
163
|
+
* then a Conversation-specific `Conv <id8>` fallback so unpopulated
|
|
164
|
+
* conversations don't render as the literal label.
|
|
165
|
+
*/
|
|
166
|
+
function pickShortLabel(node) {
|
|
167
|
+
const props = node.properties;
|
|
168
|
+
const primaryLabel = node.labels[0];
|
|
169
|
+
if (primaryLabel === 'ToolCall') {
|
|
170
|
+
const v = props.toolName;
|
|
171
|
+
if (typeof v === 'string' && v.length > 0) {
|
|
172
|
+
return v.length > 24 ? v.slice(0, 24) + '…' : v;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (primaryLabel === 'WorkflowRun') {
|
|
176
|
+
const v = props.startedAt;
|
|
177
|
+
if (typeof v === 'string' && v.length > 0) {
|
|
178
|
+
const ts = formatRunTimestamp(v);
|
|
179
|
+
if (ts)
|
|
180
|
+
return ts;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (primaryLabel === 'WorkflowStep') {
|
|
184
|
+
const v = props.label;
|
|
185
|
+
if (typeof v === 'string' && v.length > 0) {
|
|
186
|
+
return v.length > 24 ? v.slice(0, 24) + '…' : v;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else if (primaryLabel === 'Person') {
|
|
190
|
+
const given = typeof props.givenName === 'string' ? props.givenName : '';
|
|
191
|
+
const family = typeof props.familyName === 'string' ? props.familyName : '';
|
|
192
|
+
const full = [given, family].filter((s) => s.length > 0).join(' ');
|
|
193
|
+
if (full.length > 0) {
|
|
194
|
+
return full.length > 24 ? full.slice(0, 24) + '…' : full;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (primaryLabel === 'Agent') {
|
|
198
|
+
const dn = typeof props.displayName === 'string' ? props.displayName : '';
|
|
199
|
+
const slug = typeof props.slug === 'string' ? props.slug : '';
|
|
200
|
+
const v = dn.length > 0 ? dn : slug;
|
|
201
|
+
if (v.length > 0) {
|
|
202
|
+
return v.length > 24 ? v.slice(0, 24) + '…' : v;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (primaryLabel === 'Message') {
|
|
206
|
+
const role = typeof props.role === 'string' ? props.role : '';
|
|
207
|
+
const roleShort = role === 'user' ? 'user'
|
|
208
|
+
: role === 'assistant' ? 'asst'
|
|
209
|
+
: role === 'system' ? 'sys'
|
|
210
|
+
: role === 'tool' ? 'tool'
|
|
211
|
+
: null;
|
|
212
|
+
const createdAt = typeof props.createdAt === 'string' ? props.createdAt : '';
|
|
213
|
+
if (roleShort && createdAt) {
|
|
214
|
+
const ts = formatMessageTimestamp(createdAt);
|
|
215
|
+
if (ts)
|
|
216
|
+
return `${roleShort} ${ts}`;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (primaryLabel === 'ConversationArchive') {
|
|
220
|
+
const t = typeof props.title === 'string' ? props.title : '';
|
|
221
|
+
if (t.length > 0) {
|
|
222
|
+
return t.length > 24 ? t.slice(0, 24) + '…' : t;
|
|
223
|
+
}
|
|
224
|
+
return primaryLabel;
|
|
225
|
+
}
|
|
226
|
+
for (const k of ['name', 'title', 'summary', 'givenName', 'subject', 'text']) {
|
|
227
|
+
const v = props[k];
|
|
228
|
+
if (typeof v === 'string' && v.length > 0) {
|
|
229
|
+
return v.length > 24 ? v.slice(0, 24) + '…' : v;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (primaryLabel === 'Conversation') {
|
|
233
|
+
const id = typeof props.conversationId === 'string' ? props.conversationId : null;
|
|
234
|
+
if (id)
|
|
235
|
+
return `Conv ${id.slice(0, 8)}`;
|
|
236
|
+
}
|
|
237
|
+
return primaryLabel ?? '…';
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Untruncated sibling of pickShortLabel. Used by side-panel headings and
|
|
241
|
+
* tooltips where the 24-char canvas-overlap cap does not apply.
|
|
242
|
+
*/
|
|
243
|
+
function pickDisplayName(node) {
|
|
244
|
+
const props = node.properties;
|
|
245
|
+
const primaryLabel = node.labels[0];
|
|
246
|
+
if (primaryLabel === 'ToolCall') {
|
|
247
|
+
const v = props.toolName;
|
|
248
|
+
if (typeof v === 'string' && v.length > 0)
|
|
249
|
+
return v;
|
|
250
|
+
}
|
|
251
|
+
else if (primaryLabel === 'WorkflowRun') {
|
|
252
|
+
const v = props.startedAt;
|
|
253
|
+
if (typeof v === 'string' && v.length > 0) {
|
|
254
|
+
const ts = formatRunTimestamp(v);
|
|
255
|
+
if (ts)
|
|
256
|
+
return ts;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else if (primaryLabel === 'WorkflowStep') {
|
|
260
|
+
const v = props.label;
|
|
261
|
+
if (typeof v === 'string' && v.length > 0)
|
|
262
|
+
return v;
|
|
263
|
+
}
|
|
264
|
+
else if (primaryLabel === 'Person') {
|
|
265
|
+
const given = typeof props.givenName === 'string' ? props.givenName : '';
|
|
266
|
+
const family = typeof props.familyName === 'string' ? props.familyName : '';
|
|
267
|
+
const full = [given, family].filter((s) => s.length > 0).join(' ');
|
|
268
|
+
if (full.length > 0)
|
|
269
|
+
return full;
|
|
270
|
+
}
|
|
271
|
+
else if (primaryLabel === 'Agent') {
|
|
272
|
+
const dn = typeof props.displayName === 'string' ? props.displayName : '';
|
|
273
|
+
const slug = typeof props.slug === 'string' ? props.slug : '';
|
|
274
|
+
const v = dn.length > 0 ? dn : slug;
|
|
275
|
+
if (v.length > 0)
|
|
276
|
+
return v;
|
|
277
|
+
}
|
|
278
|
+
else if (primaryLabel === 'ConversationArchive') {
|
|
279
|
+
const t = typeof props.title === 'string' ? props.title : '';
|
|
280
|
+
if (t.length > 0)
|
|
281
|
+
return t;
|
|
282
|
+
return primaryLabel;
|
|
283
|
+
}
|
|
284
|
+
for (const k of ['name', 'title', 'summary', 'givenName', 'subject', 'text']) {
|
|
285
|
+
const v = props[k];
|
|
286
|
+
if (typeof v === 'string' && v.length > 0)
|
|
287
|
+
return v;
|
|
288
|
+
}
|
|
289
|
+
if (primaryLabel === 'Conversation') {
|
|
290
|
+
const id = typeof props.conversationId === 'string' ? props.conversationId : null;
|
|
291
|
+
if (id)
|
|
292
|
+
return `Conv ${id.slice(0, 8)}`;
|
|
293
|
+
}
|
|
294
|
+
return primaryLabel ?? '…';
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parity gate for the shared graph-style lib. Asserts the small set of
|
|
3
|
+
* load-bearing fixtures every UI and plugin caller relies on. Richer
|
|
4
|
+
* coverage lives in the UI's existing tests under
|
|
5
|
+
* platform/ui/app/lib/__tests__/graph-labels.test.ts and
|
|
6
|
+
* platform/ui/app/graph/__tests__/*; those tests now exercise the
|
|
7
|
+
* re-exported symbols and pass unchanged if extraction was faithful.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from "vitest";
|
|
11
|
+
import {
|
|
12
|
+
GRAPH_LABEL_COLOURS,
|
|
13
|
+
FALLBACK_LABEL_COLOUR,
|
|
14
|
+
pickDisplayLabel,
|
|
15
|
+
resolveNodeColour,
|
|
16
|
+
pickShortLabel,
|
|
17
|
+
pickDisplayName,
|
|
18
|
+
} from "../index.js";
|
|
19
|
+
|
|
20
|
+
describe("palette invariants", () => {
|
|
21
|
+
it("registers every label family with a hex colour", () => {
|
|
22
|
+
for (const [label, colour] of Object.entries(GRAPH_LABEL_COLOURS)) {
|
|
23
|
+
expect(colour, `${label} colour`).toMatch(/^#[0-9A-Fa-f]{6}$/);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("FALLBACK_LABEL_COLOUR is a hex", () => {
|
|
28
|
+
expect(FALLBACK_LABEL_COLOUR).toMatch(/^#[0-9A-Fa-f]{6}$/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("UserMessage and Preference deliberately share a hue (Person-family cue)", () => {
|
|
32
|
+
expect(GRAPH_LABEL_COLOURS.UserMessage).toBe(GRAPH_LABEL_COLOURS.Preference);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("pickDisplayLabel", () => {
|
|
37
|
+
it("returns the first registered sublabel", () => {
|
|
38
|
+
expect(pickDisplayLabel(["Conversation", "AdminConversation"])).toBe(
|
|
39
|
+
"AdminConversation",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("falls back to labels[0] when no sublabel is registered", () => {
|
|
44
|
+
expect(pickDisplayLabel(["Person", "UnregisteredSublabel"])).toBe("Person");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns null on empty labels", () => {
|
|
48
|
+
expect(pickDisplayLabel([])).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("resolveNodeColour", () => {
|
|
53
|
+
it("counts drift candidates", () => {
|
|
54
|
+
const r = resolveNodeColour(["Conversation", "PublicConversation", "AssistantMessage"]);
|
|
55
|
+
expect(r.driftCandidates).toBe(2);
|
|
56
|
+
expect(r.colour).toBe(GRAPH_LABEL_COLOURS.PublicConversation);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("pickShortLabel", () => {
|
|
61
|
+
it("Person composes from given+family", () => {
|
|
62
|
+
expect(
|
|
63
|
+
pickShortLabel({
|
|
64
|
+
id: "x",
|
|
65
|
+
labels: ["Person"],
|
|
66
|
+
properties: { givenName: "Dan", familyName: "Brett" },
|
|
67
|
+
}),
|
|
68
|
+
).toBe("Dan Brett");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("Message renders role + HH:mm:ss", () => {
|
|
72
|
+
expect(
|
|
73
|
+
pickShortLabel({
|
|
74
|
+
id: "x",
|
|
75
|
+
labels: ["Message"],
|
|
76
|
+
properties: { role: "user", createdAt: "2026-05-25T14:33:42Z" },
|
|
77
|
+
}),
|
|
78
|
+
).toBe("user 14:33:42");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("Conversation falls back to Conv <id8>", () => {
|
|
82
|
+
expect(
|
|
83
|
+
pickShortLabel({
|
|
84
|
+
id: "x",
|
|
85
|
+
labels: ["Conversation"],
|
|
86
|
+
properties: { conversationId: "abcdef0123456789" },
|
|
87
|
+
}),
|
|
88
|
+
).toBe("Conv abcdef01");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("truncates over-cap labels to 24 chars + ellipsis", () => {
|
|
92
|
+
const longName = "This is a very long string that exceeds twenty-four chars";
|
|
93
|
+
const out = pickShortLabel({
|
|
94
|
+
id: "x",
|
|
95
|
+
labels: ["LocalBusiness"],
|
|
96
|
+
properties: { name: longName },
|
|
97
|
+
});
|
|
98
|
+
expect(out.length).toBeLessThanOrEqual(25); // 24 + '…'
|
|
99
|
+
expect(out.endsWith("…")).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("pickDisplayName", () => {
|
|
104
|
+
it("untruncated counterpart to pickShortLabel", () => {
|
|
105
|
+
const longName = "This is a very long string that exceeds twenty-four chars";
|
|
106
|
+
expect(
|
|
107
|
+
pickDisplayName({
|
|
108
|
+
id: "x",
|
|
109
|
+
labels: ["LocalBusiness"],
|
|
110
|
+
properties: { name: longName },
|
|
111
|
+
}),
|
|
112
|
+
).toBe(longName);
|
|
113
|
+
});
|
|
114
|
+
});
|