From 97302f1af84c144022d8ba532273e28d6867b846 Mon Sep 17 00:00:00 2001 From: Implementer Date: Tue, 13 Oct 2026 08:55:55 +0200 Subject: [PATCH] Takes conventions v1.51.0, design v0.154.0, mcp-server v0.69.0, meta-model v0.94.0 and mental-model 57707b0 The family resync moves robertblust/conventions in `conventions.json` from v1.46.0 to v1.51.0, robertblust/design in `package.json` from v0.151.0 to v0.154.0, companygraph/mcp-server in `package.json` from v0.62.0 to v0.69.0, companygraph/meta-model in `package.json` from v0.88.0 to v0.94.0 and guestgraph/mental-model in `source.json` from 99eb28b to 57707b0. The upstream notes are at https://github.com/robertblust/conventions/releases/tag/v1.51.0, https://github.com/robertblust/design/releases/tag/v0.154.0, https://github.com/companygraph/mcp-server/releases/tag/v0.69.0, https://github.com/companygraph/meta-model/releases/tag/v0.94.0 and https://github.com/guestgraph/mental-model. Verified: `sh conventions/conventions-sync sync`, `npm install`, `npm run design`, `npm run pages`, `npm run pictures`, `npm run pages`, `npm run og`, `npm run sitemap`, `npm install`, `npm run pages`, `npm run pictures`, `npm run pages`, `npm run og`, `npm run sitemap`, `npm install`, `npm run model`, `npm run pages`, `npm run pictures`, `npm run pages`, `npm run og`, `npm run sitemap`, `npm run model`, `npm run pages`, `npm run pictures`, `npm run pages`, `npm run og`, `npm run sitemap`, `npm ci`, `cd talks/intro/tts && ./generate.py --dry-run`, `npm run test:og`, `npm run og:check`, `npm run design:check`, `npm run sitemap:check`, `GITHUB_TOKEN=${GITHUB_TOKEN:-$(gh auth token)} npm run model:check`, `npm run pages:check`, `npm run api:check`, `GITHUB_TOKEN=${GITHUB_TOKEN:-$(gh auth token)} npm run pin:check`, `npx playwright install --with-deps chromium` and `PORT=$(python3 -c 'import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1])'); ./node_modules/.bin/design serve --port $PORT & SERVER_PID=$!; sleep 1; BASE=http://127.0.0.1:$PORT npm run verify && npx design links --base http://127.0.0.1:$PORT; STATUS=$?; kill $SERVER_PID; exit $STATUS` passed. Process: Delivery Phase: Implement Track: Code --- .github/workflows/conventions.yml | 2 +- AGENTS.md | 2 +- api/og.sha | 2 +- billing/og.sha | 2 +- chat.css | 8 +- chat.js | 130 +++++++++++++++--- conventions.json | 2 +- conventions/AGENTS.md | 2 +- conventions/GERMAN.md | 2 +- conventions/GLOSSARY.md | 8 ++ conventions/conventions-check | 52 ++++++- conventions/manifest.json | 10 +- .../index.html | 13 ++ .../index.html | 13 ++ model.json | 82 ++++++++++- model/og.sha | 2 +- og.sha | 2 +- package-lock.json | 20 +-- package.json | 6 +- page.css | 11 +- principles/index.html | 2 +- principles/og.sha | 2 +- privacy/index.html | 2 +- privacy/og.sha | 2 +- problems/og.sha | 2 +- processes/og.sha | 2 +- processes/pictures/answering.sha | 2 +- processes/pictures/contribution.sha | 2 +- processes/pictures/deciding.sha | 2 +- processes/pictures/delivery.sha | 2 +- processes/pictures/feature-request.sha | 2 +- processes/pictures/narrating.sha | 2 +- sitemap.xml | 10 +- source.json | 2 +- surfaces/index.html | 2 +- surfaces/og.png | Bin 181864 -> 181993 bytes surfaces/og.sha | 2 +- talks/intro/og.sha | 2 +- talks/og.sha | 2 +- 39 files changed, 329 insertions(+), 86 deletions(-) create mode 100644 id/01a12458-5154-7010-ba0e-9aa105b2bffa/index.html create mode 100644 id/01a12458-610b-72ec-8eae-ee792bac7337/index.html diff --git a/.github/workflows/conventions.yml b/.github/workflows/conventions.yml index ef7855b..fccd772 100644 --- a/.github/workflows/conventions.yml +++ b/.github/workflows/conventions.yml @@ -5,4 +5,4 @@ on: pull_request: jobs: conventions: - uses: robertblust/conventions/.github/workflows/check.yml@v1.46.0 + uses: robertblust/conventions/.github/workflows/check.yml@v1.51.0 diff --git a/AGENTS.md b/AGENTS.md index 63f88f6..be47851 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ - + Shared conventions of the robertblust, guestgraph and companygraph organizations live in `conventions/`, vendored from robertblust/conventions at the release `conventions.json` names. Read them before writing or committing anything here. - `conventions/WRITING.md` — how we write: one voice, three registers, English and German. diff --git a/api/og.sha b/api/og.sha index bd39cb7..2850305 100644 --- a/api/og.sha +++ b/api/og.sha @@ -1 +1 @@ -b0dc13542dc3d646d3d60af1a29989be66feb93c4f8ab7ab4b7328797cd84b92 +bc3921c55199ec90d8dc506f5cd2a90b104471f2183baed6f512b60b7b85c6d5 diff --git a/billing/og.sha b/billing/og.sha index eab1465..691e7a4 100644 --- a/billing/og.sha +++ b/billing/og.sha @@ -1 +1 @@ -16ce5f5d2457f0ace4463a8eabe20c76cd6e9a337e65a80d5ea9ad99274259f5 +d528a66ad32c7d433ea4e2fe5edcb63d1b790119dc31e01278cf97682671a510 diff --git a/chat.css b/chat.css index f78c782..02be536 100644 --- a/chat.css +++ b/chat.css @@ -214,7 +214,12 @@ html[data-chat-waiting]::after{content:"ask \00b7 " attr(data-chat-waiting);pos Processes page draws them, and both beat the linked label's own color; a system's element mark is the firm brightness too, since a system is a thing and not an agent. An open position is a dashed box with no fill, as is a scenario's extension, and the frame the agents of a group - stand in is shaded. */ + stand in is shaded. A frame that stands on the ground, a use case's scope and a requirement + kind's, is shaded the same way with the accent as its border: Mermaid fills a frame with the + ground, the modal's own color, and in the dark theme draws its border black, so the frame + would not read. The host names a frame by its subgraph id, which Mermaid keeps at the end of + the cluster's own, and that is what the selector reads; an organization's groups, named + g0 and on, stay on the ground so that the agents' frame inside them stands apart. */ .rbchat-diagram-box svg .label-icon{width:1.15em;height:1.15em;vertical-align:-.2em;margin-right:.3em} .rbchat-diagram-box svg .rbchat-mark.human{color:var(--c-firm,var(--ink))!important} .rbchat-diagram-box svg .rbchat-mark.agent{color:var(--c-mid)!important} @@ -223,6 +228,7 @@ html[data-chat-waiting]::after{content:"ask \00b7 " attr(data-chat-waiting);pos .rbchat-diagram-box svg .node.open rect{fill:none!important} .rbchat-diagram-box svg .node.ext rect{fill:none!important} .rbchat-diagram-box svg .cluster.agents rect{fill:var(--press)!important} +.rbchat-diagram-box svg .cluster:is([id$="-scope"],[id$="-functional"],[id$="-quality"],[id$="-constraint"]) rect{fill:var(--press)!important;stroke:var(--c-mid)!important} .rbchat-diagram-reading{margin:.35rem .1rem 0;font-size:.8rem;line-height:1.4;color:var(--dim)} /* A page's own picture is a preview: fitted to the column, never past its own size, and opened by a click to be read; chat.js marks it. The rule reaches the box only while it is in diff --git a/chat.js b/chat.js index 23b70db..650e6b7 100644 --- a/chat.js +++ b/chat.js @@ -51,7 +51,7 @@ // rbChat.iconOf(doc) the page's icon, for the head of that line // rbChat.pick(list, n, random) n items of list, uniformly at random and without repeats // rbChat.unasked(list, messages) the titles no visitor message in the conversation has asked -// rbChat.spread(items, n, random) the titles offered, one per kind where the model groups them +// rbChat.spread(items, n, random) the titles offered, one per kind where the model groups them, its first-ranked kind first // rbChat.mentioned(content, names, items, messages) the entities an answer writes, in its order, not yet asked about // rbChat.follow(cites, items, messages, lang, random, named) the three that follow an answer, or null // rbChat.answerLang(turns, page) the language what follows the last answer is offered in @@ -62,6 +62,8 @@ // rbChat.lockupOf(doc) the header's mark and name, split as the page splits it, or null // rbChat.command(text) "new", "help" or null, for the command line's own words // rbChat.picked(text, rows) the row a bare number picks, or the text as typed +// rbChat.tryPick(facts, random) what an opening offers after the meta-model, two of what the model holds +// rbChat.connected(entities, edges) the systems a landscape row may name // rbChat.tryRows(facts, lang, random) the intro's asks, each with what comes back // rbChat.commitOf(cites) the commit the answer was read at, or null // rbChat.seconds(ms) the spinner's count @@ -122,6 +124,10 @@ try: { metaModel: "Show me the meta-model", metaModelGets: "a diagram of the types and how they refer to each other", process: "Walk me through the {name} process", processGets: "its steps as a flow, the loops back included", + organization: "Show me the organization", organizationGets: "its units as a chart, with who leads each and who sits in it", + landscape: "Show me the landscape of {name}", landscapeGets: "what it runs on, what it connects to and the data it keeps", + usecase: "Show me the use case {name}", usecaseGets: "who it serves, what it includes and what it asks of the product", + requirements: "Show me the requirements of {name}", requirementsGets: "what it is held to, every requirement a link into the model", list: "List {list} as a table", listGets: "one row each, every name a link into the model", lists: { kpi: "the KPIs", seat: "the seats", product: "the products", decision: "the decisions", value: "the values" } }, @@ -170,6 +176,10 @@ try: { metaModel: "Zeig mir das Meta-Modell", metaModelGets: "ein Diagramm der Typen und wie sie aufeinander verweisen", process: "Zeig mir den Prozess {name} Schritt für Schritt", processGets: "die Schritte als Ablauf, samt Rücksprüngen", + organization: "Zeig mir die Organisation", organizationGets: "die Einheiten als Organigramm: wer sie jeweils leitet und wer dazugehört", + landscape: "Zeig mir die Landschaft von {name}", landscapeGets: "worauf das System läuft, womit es verbunden ist und welche Daten es hält", + usecase: "Zeig mir den Anwendungsfall {name}", usecaseGets: "wem er dient, was er einbindet und was er vom Produkt verlangt", + requirements: "Zeig mir die Anforderungen an {name}", requirementsGets: "woran das Produkt gemessen wird, jede Anforderung ein Link ins Modell", list: "Liste {list} als Tabelle auf", listGets: "eine Zeile je Eintrag, jeder Name ein Link ins Modell", lists: { kpi: "die KPIs", seat: "die Sitze", product: "die Produkte", decision: "die Entscheide", value: "die Werte" } }, @@ -601,7 +611,9 @@ // The chips as a span of what the model answers: where its questions name a kind, three kinds // are picked at random and one question from each, so a visitor sees three sorts of question - // rather than three that may all be about one thing. Fewer kinds than chips fill from what is + // rather than three that may all be about one thing. Where the model ranks its kinds, the first + // chip is of the kind it ranks first among those still open, the one its owner says a visitor + // asks first, and the others span the rest at random. Fewer kinds than chips fill from what is // left, and a model whose questions name no kind is offered exactly as pick() offers it, so a // site that takes this before its model groups anything loses nothing. function spread(items, n, random){ @@ -609,9 +621,16 @@ var list = (items || []).filter(function(q){ return q && typeof q.title === "string"; }); var kinded = list.filter(function(q){ return typeof q.kind === "string" && q.kind; }); if (!kinded.length) return pick(list.map(function(q){ return q.title; }), n, rnd); - var byKind = {}, kinds = []; - kinded.forEach(function(q){ if (!byKind[q.kind]) { byKind[q.kind] = []; kinds.push(q.kind); } byKind[q.kind].push(q.title); }); - var chosen = pick(kinds, n, rnd).map(function(k){ return pick(byKind[k], 1, rnd)[0]; }); + var byKind = {}, kinds = [], rankOf = Object.create(null), first = null; + kinded.forEach(function(q){ + if (!byKind[q.kind]) { byKind[q.kind] = []; kinds.push(q.kind); } + byKind[q.kind].push(q.title); + if (typeof q.rank === "number" && isFinite(q.rank) && !(rankOf[q.kind] <= q.rank)) rankOf[q.kind] = q.rank; + }); + kinds.forEach(function(k){ if (k in rankOf && (first === null || rankOf[k] < rankOf[first])) first = k; }); + var count = Math.max(0, Number(n) || 0); + var order = first !== null && count > 0 ? [first].concat(pick(kinds.filter(function(k){ return k !== first; }), count - 1, rnd)) : pick(kinds, n, rnd); + var chosen = order.map(function(k){ return pick(byKind[k], 1, rnd)[0]; }); var taken = {}; chosen.forEach(function(t){ taken[t] = true; }); var rest = list.map(function(q){ return q.title; }).filter(function(t){ return !taken[t]; }); @@ -759,6 +778,11 @@ return { // The classic look is flat, as the family draws: the default draws shadows and gradients. startOnLoad: false, securityLevel: "strict", theme: "base", look: "classic", fontFamily: font, + // Mermaid draws a rounded node and a cylinder from a seed, and where this is left at its + // default of 0 it takes a random one for each render, so the same picture came out a few + // pixels different every time. A fixed seed makes a picture the same wherever and whenever + // it is drawn, which a deck's PDF is compared by. + handDrawnSeed: 1, // At its own size in a box that scrolls: fitted to a bubble, a wide picture's words shrink // below reading. A concept carries no attributes or methods, so its class has no empty bars. flowchart: { useMaxWidth: false }, class: { useMaxWidth: false, hideEmptyMembersBox: true }, @@ -869,17 +893,71 @@ return i >= 0 && i < rows.length ? rows[i] : t; } // The Try rows: what the chat is built to answer, each with what comes back. The meta-model - // always; a process only where the model holds one, picked at random; a list only of a kind - // the model holds at least three of, the first in this order, so no row names what is not there. + // always, then two of what the model holds, picked at random: one of its processes, its + // organization where it holds a group, the landscape of one of its systems, one of its use + // cases, what one of its products is held to where it holds a requirement, and a list of a + // kind it holds at least three of, the first in LIST_TYPES' order. No row names what the model + // does not have, and a model that holds only a process and a list is offered both, as before. var LIST_TYPES = ["kpi", "seat", "product", "decision", "value"]; - function tryRows(facts, lang, random){ - var t = strings(lang).try, rows = [[t.metaModel, t.metaModelGets]]; - var ps = facts && Array.isArray(facts.processes) ? facts.processes : []; - if (ps.length) rows.push([t.process.replace("{name}", pick(ps, 1, random)[0]), t.processGets]); - var counts = facts && facts.counts || {}; + var TRY_ROWS = 3; + // What a row asks about by name, a name short enough to stand in a row: a use case and a + // product as the model names them, and a system as connected() chooses it. + var NAMED_TYPES = ["use-case", "product"], NAME_LIMIT = 120; + // The systems a landscape row may name: those the model writes a connection on, since a + // landscape is what a system takes its data from and one that takes none draws alone; where + // the model writes no connection at all, every system. + function connected(entities, edges){ + var nameOf = {}, all = [], on = {}; + (Array.isArray(entities) ? entities : []).forEach(function(e){ + if (!e || e.type !== "system" || typeof e.name !== "string" || !e.name.length || e.name.length > NAME_LIMIT) return; + all.push(e.name); + if (typeof e.id === "string") nameOf[e.id] = e.name; + }); + (Array.isArray(edges) ? edges : []).forEach(function(g){ + if (g && typeof g.via === "string" && g.via.indexOf("Connects to.") === 0 && typeof nameOf[g.from] === "string") on[nameOf[g.from]] = true; + }); + var linked = all.filter(function(n){ return on[n] === true; }); + return linked.length ? linked : all; + } + // What an opening offers after the meta-model, as { key, name }: `key` names the row's words in + // strings().try and `name` what it asks about, so the picks are kept with the conversation and + // drawn again in either language. They stand in one order however the random falls. + function tryPick(facts, random){ + var rnd = typeof random === "function" ? random : Math.random; + var f = facts || {}, counts = f.counts || {}, names = f.names || {}, pool = []; + function named(type){ return Array.isArray(names[type]) ? names[type] : []; } + function one(list){ return pick(list, 1, rnd)[0]; } + var ps = Array.isArray(f.processes) ? f.processes : []; + if (ps.length) pool.push({ key: "process", name: one(ps) }); + if ((counts.group || 0) >= 1) pool.push({ key: "organization" }); + if (named("system").length) pool.push({ key: "landscape", name: one(named("system")) }); + if (named("use-case").length) pool.push({ key: "usecase", name: one(named("use-case")) }); + if ((counts.requirement || 0) >= 1 && named("product").length) pool.push({ key: "requirements", name: one(named("product")) }); for (var i = 0; i < LIST_TYPES.length; i++) { - if ((counts[LIST_TYPES[i]] || 0) >= 3) { rows.push([t.list.replace("{list}", t.lists[LIST_TYPES[i]]), t.listGets]); break; } + if ((counts[LIST_TYPES[i]] || 0) >= 3) { pool.push({ key: "list", name: LIST_TYPES[i] }); break; } } + var at = pick(pool.map(function(_, n){ return n; }), TRY_ROWS - 1, rnd).sort(function(x, y){ return x - y; }); + return at.map(function(n){ return pool[n]; }); + } + // The rows as the page's language words them. `facts.tries` is the picks an opening already + // made, drawn as they stand; without it the picks are made here. A pick this widget has no + // words for, kept in the tab by another release, is left out rather than drawn half. + function tryRows(facts, lang, random){ + var t = strings(lang).try, rows = [[t.metaModel, t.metaModelGets]]; + var tries = facts && Array.isArray(facts.tries) ? facts.tries : tryPick(facts, random); + tries.forEach(function(p){ + if (!p || typeof p.key !== "string") return; + var ask = t[p.key], gets = t[p.key + "Gets"]; + if (typeof ask !== "string" || typeof gets !== "string") return; + if (p.key === "list") { + if (typeof t.lists[p.name] !== "string") return; + ask = ask.replace("{list}", function(){ return t.lists[p.name]; }); + } else if (ask.indexOf("{name}") >= 0) { + if (typeof p.name !== "string" || !p.name) return; + ask = ask.replace("{name}", function(){ return p.name; }); + } + rows.push([ask, gets]); + }); return rows; } // The commit the answer was read at: the first cite whose URL names one. @@ -895,7 +973,7 @@ // The rows a number picks, as the keys line and /help name them: none, one, or a range. function rangeOf(n){ return n > 1 ? "1-" + n : n === 1 ? "1" : ""; } - window.rbChat = { md: md, readEvents: readEvents, strings: strings, link: link, refocus: refocus, asked: asked, linkQuestion: linkQuestion, nameLinks: nameLinks, heard: heard, when: when, refusalText: refusalText, citeLine: citeLine, iconOf: iconOf, pick: pick, unasked: unasked, sayIn: sayIn, asTitles: asTitles, answerLang: answerLang, spread: spread, mentioned: mentioned, mermaidConfig: mermaidConfig, nodeElement: nodeElement, diagramCaption: diagramCaption, nodeHref: nodeHref, oriented: oriented, follow: follow, place: place, placed: placed, lockupOf: lockupOf, command: command, picked: picked, tryRows: tryRows, commitOf: commitOf, seconds: seconds, rangeOf: rangeOf, versionsOf: versionsOf, graphHref: graphHref, entityOf: entityOf, graphTarget: graphTarget }; + window.rbChat = { md: md, readEvents: readEvents, strings: strings, link: link, refocus: refocus, asked: asked, linkQuestion: linkQuestion, nameLinks: nameLinks, heard: heard, when: when, refusalText: refusalText, citeLine: citeLine, iconOf: iconOf, pick: pick, unasked: unasked, sayIn: sayIn, asTitles: asTitles, answerLang: answerLang, spread: spread, mentioned: mentioned, mermaidConfig: mermaidConfig, nodeElement: nodeElement, diagramCaption: diagramCaption, nodeHref: nodeHref, oriented: oriented, follow: follow, place: place, placed: placed, lockupOf: lockupOf, command: command, picked: picked, tryPick: tryPick, connected: connected, tryRows: tryRows, commitOf: commitOf, seconds: seconds, rangeOf: rangeOf, versionsOf: versionsOf, graphHref: graphHref, entityOf: entityOf, graphTarget: graphTarget }; // ─── The page ───────────────────────────────────────────────────────────────────────────── var tag = document.currentScript; @@ -1477,7 +1555,7 @@ // container currently in the log, if any, and `qNext` whether it follows an answer rather than // opening an empty conversation, which is all that tells its two names apart. `qPicked` is // the titles that box offers, kept with the conversation while it stands. - var qList = null, qFetch = null, qBox = null, qPicked = null, qNext = false, qFacts = { processes: [], counts: {}, versions: null }; + var qList = null, qFetch = null, qBox = null, qPicked = null, qNext = false, qFacts = { processes: [], counts: {}, names: {}, versions: null }; var Q_TIMEOUT = 8000; // The titles to offer, read from the site's own parsed model rather than asked of the chat @@ -1516,14 +1594,22 @@ // of each type the model holds, so a row never names what the model does not have. var counts = {}; entities.forEach(function(e){ if (e && typeof e.type === "string") counts[e.type] = (counts[e.type] || 0) + 1; }); - qFacts = { processes: entities.filter(function(e){ return e && e.type === "process" && typeof e.name === "string" && e.name.length > 0; }).map(function(e){ return e.name; }), counts: counts, versions: versionsOf(j) }; + // What a row asks about by name, and the rank the model gives each kind of question. + var names = {}, ranks = Object.create(null); + entities.forEach(function(e){ + if (!e || typeof e.name !== "string" || !e.name.length) return; + if (NAMED_TYPES.indexOf(e.type) >= 0 && e.name.length <= NAME_LIMIT) (names[e.type] = names[e.type] || []).push(e.name); + if (e.type === "question-kind" && e.fields && e.fields.rank != null && String(e.fields.rank).trim() !== "" && isFinite(Number(e.fields.rank))) ranks[e.name] = Number(e.fields.rank); + }); + names.system = connected(entities, j && j.edges); + qFacts = { processes: entities.filter(function(e){ return e && e.type === "process" && typeof e.name === "string" && e.name.length > 0; }).map(function(e){ return e.name; }), counts: counts, names: names, versions: versionsOf(j) }; (j && Array.isArray(j.edges) ? j.edges : []).forEach(function(g){ if (!g || types[g.from] !== "question" || typeof g.via !== "string" || g.via.indexOf("Rests on.") !== 0) return; (rests[g.from] = rests[g.from] || []).push({ id: g.to, type: types[g.to] || null }); }); return entities .filter(function(e){ return e && e.type === "question" && typeof e.name === "string" && e.name.length > 0; }) - .map(function(e){ return { id: typeof e.id === "string" ? e.id : null, title: e.name, kind: e.fields && typeof e.fields.kind === "string" ? e.fields.kind : null, rests: rests[e.id] || [] }; }) + .map(function(e){ var kind = e.fields && typeof e.fields.kind === "string" ? e.fields.kind : null; return { id: typeof e.id === "string" ? e.id : null, title: e.name, kind: kind, rank: kind !== null && kind in ranks ? ranks[kind] : null, rests: rests[e.id] || [] }; }) .filter(function(q){ return q.title.length <= LIMIT; }); }); }) @@ -1845,8 +1931,10 @@ if (mine !== introRun || !introEl) return; var t = strings(langNow()); var seen = asTitles(list, messages); - if (!introPick) introPick = { processes: f.processes.length ? pick(f.processes, 1) : [], questions: spread(list.filter(function(q){ return unasked([q.title], seen).length; }), 3) }; - var rows = tryRows({ processes: introPick.processes, counts: f.counts }, langNow()); + if (!introPick) introPick = { tries: tryPick(f), questions: spread(list.filter(function(q){ return unasked([q.title], seen).length; }), 3) }; + // Picks kept by a release that kept a process alone are made again, once. + if (!Array.isArray(introPick.tries)) introPick.tries = tryPick(f); + var rows = tryRows({ tries: introPick.tries }, langNow()); groups.appendChild(el("p", "rbchat-label", t.tryLabel)); menu(groups, rows, 0); var picked = introPick.questions.map(function(q){ return [asOffered(q)]; }); @@ -2326,8 +2414,8 @@ // Only a panel restored open shows its intro now; a closed one gets it from open(), since // the intro reads the model file and that read waits for the panel. reading = was.at && typeof was.at.turn === "number" ? was.at : { end: true }; - // The intro's picks, so it offers the same process and questions it offered on the last page. - if (was.pick && Array.isArray(was.pick.processes) && Array.isArray(was.pick.questions)) introPick = was.pick; + // The intro's picks, so it offers the same rows and questions it offered on the last page. + if (was.pick && Array.isArray(was.pick.questions)) introPick = was.pick; was.turns.forEach(function(t){ if (t.role === "user") { var u = bubble("user"); u.textContent = t.content; u.setAttribute("data-turn", turns.length); messages.push({ role: "user", content: t.content }); turns.push({ role: "user", content: t.content }); return; } var ans = bubble("assistant"), body = el("div", "rbchat-body"); diff --git a/conventions.json b/conventions.json index eed39a7..c4059d6 100644 --- a/conventions.json +++ b/conventions.json @@ -1 +1 @@ -{ "repo": "robertblust/conventions", "tag": "v1.46.0", "exclude": ["node_modules", "docs/superpowers"], "format-exclude": ["node_modules"] } +{ "repo": "robertblust/conventions", "tag": "v1.51.0", "exclude": ["node_modules", "docs/superpowers"], "format-exclude": ["node_modules"] } diff --git a/conventions/AGENTS.md b/conventions/AGENTS.md index 2568465..9521da8 100644 --- a/conventions/AGENTS.md +++ b/conventions/AGENTS.md @@ -1,4 +1,4 @@ - + Shared conventions of the robertblust, guestgraph and companygraph organizations live in `conventions/`, vendored from robertblust/conventions at the release `conventions.json` names. Read them before writing or committing anything here. - `conventions/WRITING.md` — how we write: one voice, three registers, English and German. diff --git a/conventions/GERMAN.md b/conventions/GERMAN.md index a919913..363988f 100644 --- a/conventions/GERMAN.md +++ b/conventions/GERMAN.md @@ -45,7 +45,6 @@ selbstständig → selbständig Offener Kern → Open Core Open Source → quelloffen Takeaway → Fazit -Requirements → Anforderungen ``` A bare count of the years, «fünfundzwanzig Jahre» without «über», is refused by a rule of its own in the check, because a phrase list cannot say "unless preceded by". @@ -60,6 +59,7 @@ Each row is a sentence the editor flagged and the owner settled, kept where it g | «eine einzige Quelle der Wahrheit» | «eine einzige verbindliche Quelle» | The English picture read as a calque. | | «Ein Konzept, erklärt» | «Ein Konzept im Überblick» | The label copied the English. | | «Der Haken» | «Der Aufhänger» | The hook of a talk; «Haken» is a catch. | +| «das Anforderungs-Pack» | «das Requirements-Pack» | A pack keeps its English name, as «das Landscape-Pack» and «das Software-Pack» do; the generic noun stays «Anforderungen», and the type is `Anforderung`, the glossary's row. The refused form that turned every Requirements into Anforderungen was wrong in this sentence, so it left the fence. | | «wie Arbeit fliesst» | «wie die Arbeit abläuft» | The English picture of flow. | | «Menschen richten sich schneller aus» | «Menschen finden schneller eine gemeinsame Linie» | *Align* word for word. | | «Zusammenführen ist einfach. Falsch liegen ist teuer.» | «Treffer finden ist einfach. Danebenliegen ist teuer.» | *Matching* is finding a match, not merging. | diff --git a/conventions/GLOSSARY.md b/conventions/GLOSSARY.md index ad2b657..df1c48b 100644 --- a/conventions/GLOSSARY.md +++ b/conventions/GLOSSARY.md @@ -42,6 +42,7 @@ The German cells are inline code because the prose check reads no language and a | MCP server | MCP server | `MCP-Server` | The server that answers over MCP; `companygraph/mcp-server` is the package. Masculine, `der MCP-Server`. | | MCP Registry | MCP Registry | `MCP Registry` | The public index a server is published to. A proper name, English in both languages, and feminine: `in der MCP Registry`, never `im`. No page carried German for it before, which is why the gender is recorded here rather than left to the next sentence. | | AI agent | AI agent | `KI-Agent` | The reader a model serves beside a person. Masculine, `der KI-Agent`, plural `die KI-Agenten`, the form blust.ch's ideas page already uses. | +| AI Act | AI Act | `AI Act` | The EU's regulation on artificial intelligence, whose Article 50 asks a chat to say that an AI answers. A name, English in both languages, as `MCP` and `core` are, so a reader matches the German page to the English one; masculine, `der AI Act`. Not `KI-Verordnung`, the German press's term, set aside on 2026-10-11 so both pages carry the one name. | | read-only | read-only | `nur lesend` | Of a server that answers and never writes. Not `schreibgeschützt`, which says write-protected and describes what may be done to a file rather than what a server offers. | | shipped | shipped | `ausgeliefert` | Of a thing released and running, as against designed or built. Not `gebaut`, which says only built, and not `fertig`, which says finished. | | entity | entity | `Entität` | One file in the model, of whatever type. The form blust.ch's model page uses. | @@ -83,6 +84,13 @@ The German cells are inline code because the prose check reads no language and a | actor claim | actor claim | `Actor-Claim` | A claim in a JWT that names who acts. Masculine, `der Actor-Claim`. | | steward | steward | `verantwortliche Person` | Who reviews a proposed match; plural `die Mitarbeitenden, die prüfen`. A hotel reader does not know Steward. | | API (surface) | surface | `API` | An API as what a caller reaches, `die API`. Not `Schnittstelle`, and not `Surface`, which is a page the model is published on. | +| interface (between systems) | interface | `Schnittstelle` | What a connection between two systems goes through, as a landscape's `As` column names it: an API, a file transfer, a queue. Feminine, `die Schnittstelle`. An API a caller reaches stays `API`, the row above. | +| service (of a system) | service | `Service` | What a system exposes to other systems, the landscape pack's type. Masculine, `der Service`, as Swiss IT writes it; not `Dienst`, which beside `System` and `Plattform` reads as a Windows service or a public one. | +| landscape (the picture) | landscape | `Landschaft` | The picture of one system among those it runs on, takes its data from and gives data to, and the chat's caption for it: «Zeig mir die Landschaft von Chat server». Feminine, `die Landschaft`. Not `Systemlandschaft`, the commoner word in Swiss IT, because the caption and the pack say landscape alone; the pack itself keeps its English name, `das Landscape-Pack`. | +| organization chart | organization chart | `Organigramm` | The picture of a company's units, with who leads each and who belongs to it: «die Einheiten als Organigramm». Neuter, `das Organigramm`. The caption over the picture stays `Organisation`, and `Diagramm` is any drawn figure. | +| requirements pack | requirements pack | `Requirements-Pack` | The pack for a company that says what its products must do before it builds them, with the types use case and requirement. Neuter, `das Requirements-Pack`, the English name kept as every pack's is. | +| use case | use case | `Anwendungsfall` | The requirements pack's type: one goal for one actor and every way of reaching it, in Cockburn's form. Masculine, `der Anwendungsfall`, Cockburn's German; its scope is `der Geltungsbereich`, its trigger `der Auslöser`, an extension `die Erweiterung`, its primary actor `der Hauptakteur`. | +| requirement (the type) | requirement | `Anforderung` | The requirements pack's type: one statement a product is held to, with its kind, `die Art`, its modality, `muss`, `sollte` or `darf`, and its fit criterion, `das Erfüllungskriterium`. Feminine, `die Anforderung`; a constraint is `die Randbedingung`. | | credit meter | credit meter | `Abrechnung nach Guthaben` | Billing against a prepaid balance. | | backfill | backfill | `Historischer Import` | The first import of the history before live data. | | walk-in | walk-in | `Walk-in` | A guest without a reservation, as Swiss hotels say it; plural `Walk-ins`. | diff --git a/conventions/conventions-check b/conventions/conventions-check index a7f53fe..b00a070 100755 --- a/conventions/conventions-check +++ b/conventions/conventions-check @@ -1,6 +1,7 @@ #!/bin/sh # conventions-check — hold a member's own Markdown to WRITING.md: American English and the -# spaced em-dash. Fenced code and inline code are not prose and are dropped before the scan; +# spaced em-dash. Fenced code and inline code are not prose and are dropped before the scan; the +# stem scan also skips frontmatter, the first H1 of a page and quoted text (see below); # folders under "exclude" in conventions.json are someone else's prose and are not read, nor is # anything git ignores, which the shared job never sees either. # @@ -31,6 +32,12 @@ PIN=$ROOT/conventions.json # way but on the letter that follows, since British and American diverge right there. Case is # folded by lowering the line before matching, not by the pattern, since IGNORECASE is a GNU # extension awk on macOS does not have. +# What the stem scan does not read: text in backticks, text in guillemets «…», the page's H1 line +# and the YAML frontmatter. A name is the company's and is quoted, not spelled, so a German name +# that holds a stem, or a product's own feature name, is not a spelling. Only the stem scan skips +# these spans; the closed-dash check below still reads them. A quote is cut within its line; a +# pair spanning two lines is read. Curly double quotes “…” are cut like guillemets, since an +# English name is quoted the en-US way. STEMS='organis(e|a|i)|colour|behaviour|centre|licence|recognis|modelling|catalogue|grey([^a-z]|$)|judgement|artefact|whilst|amongst|favour|honour|analys(e|ed|ing)([^a-z]|$)|optimis(e|a|i)|normalis|serialis|initialis(e|a|i)|visualis|minimis(e|a|i)|prioritis|customis|summaris|categoris|parameteris|rasteris|unrecognis|cancelled|travelled|labelled|aluminium|instalment|neighbour|totalled|defence|offence|programmes?([^a-z]|$)|fulfil([^a-z]|$)|enrol([^a-z]|$)|skilful|ageing|cosy([^a-z]|$)|focuss|cheque([^a-z]|$)|tyre([^a-z]|$)|kerb([^a-z]|$)|storey|mould|plough|draught|sceptic|generalis(e|ed|es|ing|ation|ations)|specialis(e|ed|es|ing|ation)|realis(e|ed|es|ing|ation)|materialis(e|ed|es|ing|ation)|penalis(e|ed|es|ing|ation)|singularis(e|ed|es|ing|ation)|authoris(e|ed|es|ing|ation)|emphasis(e|ed|es|ing)|criticis(e|ed|es|ing)|capitalis(e|ed|es|ing|ation)|formalis(e|ed|es|ing|ation)|finalis(e|ed|es|ing|ation)|stabilis(e|ed|es|ing|ation)|utilis(e|ed|es|ing|ation)|harmonis(e|ed|es|ing|ation)|standardis(e|ed|es|ing|ation)|synchronis(e|ed|es|ing|ation)|mobilis(e|ed|es|ing|ation)|memoris(e|ed|es|ing|ation)|apologis(e|ed|es|ing)|synthesis(e|ed|es|ing)|hypothesis(e|ed|es|ing)|paralys(e|ed|es|ing)|catalys(e|ed|es|ing)|modelled|modeller|flavour|harbour|labour|rumour|humour|vigour|rigour|armour|endeavour|savour|odour|valour|metre([^a-z]|$)|metres|litre|fibre|theatre|calibre|travelling|signalling|cancelling|fuelled|levelled|channelled|counsellor|marvellous|jewellery' DASH='[^ ]—|—[^ ]' @@ -76,8 +83,12 @@ if [ -z "$files" ]; then fi hits=$(printf '%s\n' "$files" | while IFS= read -r f; do - # Drop fenced code and inline code, then scan what is left of each line for a stem or a - # closed dash; the match is against the line's own content, never the path awk read it from. + # Drop fenced code, inline code and URLs, then scan what is left of each line for a stem or a + # closed dash. The stem scan alone also skips frontmatter, the page's first H1 and quoted + # text; the dash check reads those as it did before, with backticks and URLs stripped. The match is against the line's own content, never + # the path awk read it from. + # An address is fixed by the site it points to, as a name is, so it ends at a space, a closing + # parenthesis or angle bracket, or a table pipe, and a Markdown link's text is still read. # A fence left open at end of file is a hit of its own, not a silent skip of the rest. awk -v stems="$STEMS" -v dash="$DASH" -v root="$ROOT" ' function relpath( p, prefix) { @@ -86,19 +97,46 @@ hits=$(printf '%s\n' "$files" | while IFS= read -r f; do if (index(p, prefix) == 1) p = substr(p, length(prefix) + 1) return p } - /^```/ { fence = !fence; next } - fence { next } + # Cut the text between o and c from s. The glyph lengths are taken with length(), so the + # cut is right in a byte awk and in a character awk alike. + function cut(s, o, c, a, b, lo, lc) { + lo = length(o); lc = length(c) + while ((a = index(s, o)) > 0 && (b = index(substr(s, a + lo), c)) > 0) + s = substr(s, 1, a - 1) substr(s, a + lo + b - 1 + lc) + return s + } + # A byte order mark is dropped by its bytes in a string, which every awk reads alike. + FNR == 1 && index($0, "\357\273\277") == 1 { $0 = substr($0, length("\357\273\277") + 1) } + FNR == 1 && /^---[ \t\r]*$/ { fm = 1; next } + fm && /^---[ \t\r]*$/ { fm = 0; next } + # A frontmatter that is never closed ends at the first line that is none of these: blank, + # indented, a "- item" at column 0, a "#" comment or a key line. A stray opener cannot hide + # the whole page, though a malformed page whose --- is never closed may still hide a line + # that looks like a key. + fm && !/^[ \t\r]*$/ && !/^[ \t]/ && !/^- / && !/^#/ && !/^["\047]?[$@A-Za-z0-9_][A-Za-z0-9_ .-]*["\047]?:( |$)/ { fm = 0 } + !fm && /^```/ { fence = !fence; next } + fence { next } { line = $0 sub(/\r$/, "", line) + raw = line + # Only the stem scan skips frontmatter, the first H1 of the page and quoted text; the + # dash check below reads the line as it did before. + skip = fm + if (!fm && !h1 && line ~ /^# /) { h1 = 1; skip = 1 } gsub(/`[^`]*`/, "", line) + line = cut(line, "«", "»") + line = cut(line, "“", "”") + gsub(/https?:\/\/[^ )>|]*/, "", line) low = tolower(line) - if (match(low, stems)) { + if (!skip && match(low, stems)) { w = substr(low, RSTART, RLENGTH) sub(/[^a-z]$/, "", w) printf "✗ %s:%d: %s\n", relpath(), FNR, w } - if (match(line, dash)) printf "✗ %s:%d: closed em-dash\n", relpath(), FNR + gsub(/`[^`]*`/, "", raw) + gsub(/https?:\/\/[^ )>|]*/, "", raw) + if (match(raw, dash)) printf "✗ %s:%d: closed em-dash\n", relpath(), FNR } END { if (fence) printf "✗ %s:%d: unclosed code fence\n", relpath(), FNR } ' "$f" diff --git a/conventions/manifest.json b/conventions/manifest.json index e36c1e3..f304766 100644 --- a/conventions/manifest.json +++ b/conventions/manifest.json @@ -1,6 +1,6 @@ { "repo": "robertblust/conventions", - "tag": "v1.46.0", + "tag": "v1.51.0", "files": { "conventions/WRITING.md": "sha256:51d9a902ec3038392160df07f86aba98d34db1d4dbfaa5f4a647590eaccfb390", "conventions/WORKING.md": "sha256:3a7a6b4ab1bd23f2407fc6ada2dd0512710a49d233fa7c3342951f6fc526ab23", @@ -8,13 +8,13 @@ "conventions/PINS.md": "sha256:68df67ae820a8673d3130582aa829d4283176d3c8ec0a69adda3132a1a755b93", "conventions/WRITER.md": "sha256:bc3b92683130b75993f824f0e34073d4344846c91be2009fd90c39099fd4fd48", "conventions/TRANSLATOR.md": "sha256:3a3d2d0916d7230cbc760bced1be249b11cc516956fb41ad010b3216f23024ba", - "conventions/GLOSSARY.md": "sha256:429733de608d68142cc261dc48aa4fdf0dde3dd002902f0af413a63e6467f9b9", - "conventions/GERMAN.md": "sha256:e8b311ed4b48cc2ded964bbaa0cfcf70178cdbd8072cd467b586c2bfa0cb949b", + "conventions/GLOSSARY.md": "sha256:f29b2a31e171f9666e70e3c0ab3290876fcd90846f99c86a12a6ac56c16acdf1", + "conventions/GERMAN.md": "sha256:6a0ab79fd52f93d1361aceb83a1771ce6448124d5ea707af3d71baf9efe35e11", "conventions/EDITOR.md": "sha256:e65d0b2893ac841faefede9b873f7e33c1fd7b02bedf97d37d5362c0e83c08ea", "conventions/BACKREADER.md": "sha256:36b12d4bee8e4fe190469e2f550d50d8a7f6dcab6bec573da17eb1c165c417cd", - "conventions/AGENTS.md": "sha256:861c1ec80624afb6e87a993922a18ea101cc718ace94e4237c7da89bf6367f2b", + "conventions/AGENTS.md": "sha256:7fba72d9d4c76e269c29fbe014adfb2e03861ad9e3e4c73af594929167ceeac4", "conventions/conventions-sync": "sha256:a190ff79c2d8d3f6f6d9a597e9f261654f2419d30e2ac43abf6c64ebe2c47ed9", - "conventions/conventions-check": "sha256:fb55ca4b4cfc5caa0d346ad6c705d4402125155f95db6c974099a8112e275c69", + "conventions/conventions-check": "sha256:4a633cc53579f4577939702350c439bbb58ebed4ccbf6736d94c01e5fc160147", "conventions/conventions-format": "sha256:2911a0d30dda1bc0f621522a9538b3e39704eee03458e65ba320eccbf39b5c7f", "conventions/markdown-rules.cjs": "sha256:5cbf6120bf16e358b3a4e362015d054f0cd6e47ddfb0e5fe5ac17143b2bdef26", "conventions/vscode-settings.json": "sha256:86d3c951f7c06d86ee3bc828ad6e49fac3531be98117a7a590cc75da7ba3600a", diff --git a/id/01a12458-5154-7010-ba0e-9aa105b2bffa/index.html b/id/01a12458-5154-7010-ba0e-9aa105b2bffa/index.html new file mode 100644 index 0000000..91917d6 --- /dev/null +++ b/id/01a12458-5154-7010-ba0e-9aa105b2bffa/index.html @@ -0,0 +1,13 @@ + + + + + +Service + + + + +

Go on to Service.

+ + diff --git a/id/01a12458-610b-72ec-8eae-ee792bac7337/index.html b/id/01a12458-610b-72ec-8eae-ee792bac7337/index.html new file mode 100644 index 0000000..d755ade --- /dev/null +++ b/id/01a12458-610b-72ec-8eae-ee792bac7337/index.html @@ -0,0 +1,13 @@ + + + + + +Connector + + + + +

Go on to Connector.

+ + diff --git a/model.json b/model.json index f2a62ca..645f2c2 100644 --- a/model.json +++ b/model.json @@ -1,6 +1,6 @@ { - "commit": "99eb28b555937140e101cdfb79687a67fef5d06b", - "core": "0.64.0", + "commit": "57707b03bc351f0b5a038ba1167f56b6f8478379", + "core": "0.69.0", "root": "GuestGraph", "rootId": "01a0d2e2-f788-788d-ac26-56e9ef94fdd2", "types": [ @@ -106,6 +106,12 @@ "owner": null, "singular": false }, + { + "type": "product-kind", + "folder": "product-kinds", + "owner": null, + "singular": false + }, { "type": "profile", "folder": "profiles", @@ -9838,6 +9844,48 @@ "owner": null, "path": "model/processing-activities/keeping-the-chat-s-questions.md" }, + { + "id": "01a12458-610b-72ec-8eae-ee792bac7337", + "address": "product-kinds/connector", + "type": "product-kind", + "name": "Connector", + "tagline": "Software that brings one source system's data into the guest graph, run beside the engine.", + "fields": { + "id": "01a12458-610b-72ec-8eae-ee792bac7337", + "source": "Local", + "rank": "20" + }, + "sections": [ + { + "heading": "What it means", + "text": "A product of this kind is opened by whoever operates the engine for a hotel that keeps its stays in that source system; it holds no data of its own and serves nothing but the engine. The Apaleo Connector is this kind. The service it feeds is Service.", + "tables": [] + } + ], + "owner": null, + "path": "model/product-kinds/connector.md" + }, + { + "id": "01a12458-5154-7010-ba0e-9aa105b2bffa", + "address": "product-kinds/service", + "type": "product-kind", + "name": "Service", + "tagline": "Software a hotel, a group or an integrator runs itself and every other system calls.", + "fields": { + "id": "01a12458-5154-7010-ba0e-9aa105b2bffa", + "source": "Local", + "rank": "10" + }, + "sections": [ + { + "heading": "What it means", + "text": "A product of this kind is opened by whoever operates it for a hotel and called by every system that needs its answer; it holds data of its own. The GuestGraph Engine is this kind. Software that only brings data from one source system into it is Connector.", + "tables": [] + } + ], + "owner": null, + "path": "model/product-kinds/service.md" + }, { "id": "01a0d2e3-bec0-765d-89d6-5fc9eaaac662", "address": "products/apaleo-connector", @@ -9847,7 +9895,8 @@ "fields": { "id": "01a0d2e3-bec0-765d-89d6-5fc9eaaac662", "source": "Local", - "domain": "Integration" + "domain": "Integration", + "kind": "Connector" }, "sections": [], "owner": null, @@ -9862,7 +9911,8 @@ "fields": { "id": "01a0d2e3-bec0-71a1-88ca-2b85827bd967", "source": "Local", - "domain": "Guest identity" + "domain": "Guest identity", + "kind": "Service" }, "sections": [], "owner": null, @@ -14465,6 +14515,12 @@ "via": "domain", "attrs": {} }, + { + "from": "01a0d2e3-bec0-71a1-88ca-2b85827bd967", + "to": "01a12458-5154-7010-ba0e-9aa105b2bffa", + "via": "kind", + "attrs": {} + }, { "from": "01a0d2e3-bec0-71a1-88ca-2b85827bd967", "to": "01a0d2e2-f788-7d70-903e-402e3f765e65", @@ -14817,6 +14873,12 @@ "via": "domain", "attrs": {} }, + { + "from": "01a0d2e3-bec0-765d-89d6-5fc9eaaac662", + "to": "01a12458-610b-72ec-8eae-ee792bac7337", + "via": "kind", + "attrs": {} + }, { "from": "01a0d2e3-bec0-765d-89d6-5fc9eaaac662", "to": "01a0d2e2-f788-7d70-903e-402e3f765e65", @@ -19329,6 +19391,18 @@ "to": "01a0dd52-d3a0-7afb-a186-0025d9fb5480", "via": "status", "attrs": {} + }, + { + "from": "01a12458-5154-7010-ba0e-9aa105b2bffa", + "to": "01a0d2e2-f788-7d70-903e-402e3f765e65", + "via": "source", + "attrs": {} + }, + { + "from": "01a12458-610b-72ec-8eae-ee792bac7337", + "to": "01a0d2e2-f788-7d70-903e-402e3f765e65", + "via": "source", + "attrs": {} } ], "repo": "guestgraph/mental-model" diff --git a/model/og.sha b/model/og.sha index dda8516..14ce92b 100644 --- a/model/og.sha +++ b/model/og.sha @@ -1 +1 @@ -f8fee0c7609393753f7c4c1e9d1bae034abae9b0a2d8a97d26f1bb9e1fd00dc9 +efdff15a321d4e64b2846f0139f6cf2c50348072e981e62be103162b8695d4ee diff --git a/og.sha b/og.sha index cc6025b..76adfb7 100644 --- a/og.sha +++ b/og.sha @@ -1 +1 @@ -c319fa53115f3372ea844e5448f8063d9a864e14a0d2e785bd8f271d1bb20029 +7ea8dde35c284c806d0d1ca6f5a1d008bc45ac54c2d5dabcaef04818a4412250 diff --git a/package-lock.json b/package-lock.json index 942c3f8..a09908b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,11 @@ "name": "guestgraph-io", "version": "1.0.0", "dependencies": { - "companygraph-meta-model": "github:companygraph/meta-model#v0.88.0" + "companygraph-meta-model": "github:companygraph/meta-model#v0.94.0" }, "devDependencies": { - "@robertblust/design": "github:robertblust/design#v0.151.0", - "companygraph-mcp-server": "github:companygraph/mcp-server#v0.62.0", + "@robertblust/design": "github:robertblust/design#v0.154.0", + "companygraph-mcp-server": "github:companygraph/mcp-server#v0.69.0", "pdf-lib": "^1.17.1", "playwright": "^1.63.0", "yaml": "^2.8.1" @@ -96,8 +96,8 @@ } }, "node_modules/@robertblust/design": { - "version": "0.151.0", - "resolved": "git+ssh://git@github.com/robertblust/design.git#65b78c6310af99370e594d48de0f8bff6a682448", + "version": "0.154.0", + "resolved": "git+ssh://git@github.com/robertblust/design.git#812a39bab81d0f67e0a5a953d569b1b397e4a93f", "dev": true, "license": "Apache-2.0", "bin": { @@ -108,14 +108,14 @@ } }, "node_modules/companygraph-mcp-server": { - "version": "0.62.0", - "resolved": "git+ssh://git@github.com/companygraph/mcp-server.git#8e91cb3594a87826a8909c0cd536475a510d77cd", + "version": "0.69.0", + "resolved": "git+ssh://git@github.com/companygraph/mcp-server.git#1815507b4bb4bf1fb31ecb984bc848cc602e666b", "dev": true, "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/node": "^2.1.1", "@modelcontextprotocol/server": "^2.0.0", - "companygraph-meta-model": "github:companygraph/meta-model#v0.88.0", + "companygraph-meta-model": "github:companygraph/meta-model#v0.94.0", "zod": "^4.2.0" }, "bin": { @@ -129,8 +129,8 @@ } }, "node_modules/companygraph-meta-model": { - "version": "0.88.0", - "resolved": "git+ssh://git@github.com/companygraph/meta-model.git#93e13f7288312730e7a053d2718b8ec5b4335ef1", + "version": "0.94.0", + "resolved": "git+ssh://git@github.com/companygraph/meta-model.git#4088f5a1d23be63f43953ed1eae5eb1740f6eb8d", "license": "Apache-2.0", "bin": { "companygraph": "bin/companygraph.mjs" diff --git a/package.json b/package.json index b0ae402..f155e2c 100644 --- a/package.json +++ b/package.json @@ -25,13 +25,13 @@ "pin:check": "node pin-check.mjs" }, "devDependencies": { - "@robertblust/design": "github:robertblust/design#v0.151.0", - "companygraph-mcp-server": "github:companygraph/mcp-server#v0.62.0", + "@robertblust/design": "github:robertblust/design#v0.154.0", + "companygraph-mcp-server": "github:companygraph/mcp-server#v0.69.0", "pdf-lib": "^1.17.1", "playwright": "^1.63.0", "yaml": "^2.8.1" }, "dependencies": { - "companygraph-meta-model": "github:companygraph/meta-model#v0.88.0" + "companygraph-meta-model": "github:companygraph/meta-model#v0.94.0" } } diff --git a/page.css b/page.css index bcfe1b6..f4dcf3a 100644 --- a/page.css +++ b/page.css @@ -1,5 +1,5 @@ /* @robertblust/design — page.css, assembled from the shared blocks: reset v3 · link v1 · - header v18 · title v2 · lines v3 · footer v3 · principles v1 · processes v1 · surfaces v1 · + header v18 · title v2 · lines v3 · footer v3 · principles v1 · processes v1 · surfaces v2 · index v2 · home v1. Editing this file in a site does nothing: the next npm run design overwrites it. */ @@ -397,7 +397,10 @@ footer a[aria-current="page"]{color:var(--c-firm)} .lineage{position:relative; display:grid; grid-template-columns:minmax(0,13rem) minmax(0,1fr); column-gap:clamp(3rem,7vw,6rem); align-items:center; margin-top:1.4rem} .wires{position:absolute; inset:0; width:100%; height:100%; overflow:visible; pointer-events:none} -.wires path{fill:none; stroke:var(--rule); stroke-width:1.5; transition:stroke .2s ease} +/* A wire at rest is the candidate color, as the stage's resting edge is. --rule draws the + boxes, which their text holds in place; a wire in it was a hairline with nothing beside it, + and on the dark ground it was lost. */ +.wires path{fill:none; stroke:var(--c-weak); stroke-width:1.5; transition:stroke .2s ease} .wires path.hand{stroke-dasharray:5 5} /* --c-path is the token for exactly this: the way back to the root from what is focused. */ .wires path.on{stroke:var(--c-path); stroke-width:2} @@ -435,9 +438,9 @@ footer a[aria-current="page"]{color:var(--c-firm)} .wires{display:none} .lineage{grid-template-columns:minmax(0,1fr); row-gap:0} .ln-model{justify-self:start} - .ln-groups{gap:0; margin-left:1rem; padding:1.2rem 0 0 1.1rem; border-left:1.5px solid var(--rule)} + .ln-groups{gap:0; margin-left:1rem; padding:1.2rem 0 0 1.1rem; border-left:1.5px solid var(--c-weak)} .ln-group{grid-template-columns:minmax(0,1fr); padding-bottom:1.2rem} - .ln-surfaces{margin-left:.7rem; padding-left:1rem; border-left:1.5px solid var(--rule)} + .ln-surfaces{margin-left:.7rem; padding-left:1rem; border-left:1.5px solid var(--c-weak)} .ln-group.hand .ln-surfaces{border-left-style:dashed} } diff --git a/principles/index.html b/principles/index.html index ace083a..2dc8008 100644 --- a/principles/index.html +++ b/principles/index.html @@ -317,7 +317,7 @@

The core stays open

-

Generated from guestgraph/mental-model@99eb28b — model/vision.md and model/values/. That repository is an instance of CompanyGraph, a meta-model for describing a company as a graph of Markdown.

+

Generated from guestgraph/mental-model@57707b0 — model/vision.md and model/values/. That repository is an instance of CompanyGraph, a meta-model for describing a company as a graph of Markdown.

diff --git a/principles/og.sha b/principles/og.sha index a63f273..581cf1e 100644 --- a/principles/og.sha +++ b/principles/og.sha @@ -1 +1 @@ -78cc3731f6a85a95229672c8f3f6833fb1aa40f364003f493f239cfd3d32280e +2a612b23ff5867c32540ddda8da054942321d356a54373b250172228495243f2 diff --git a/privacy/index.html b/privacy/index.html index f85254e..aa9fbf5 100644 --- a/privacy/index.html +++ b/privacy/index.html @@ -328,7 +328,7 @@

Where your data goes

-
On guestgraph.io
Your visit
@99eb28b
+
On guestgraph.io
Your visit
@57707b0