♻️ Rename app.graph.overlay to app.graph.relation-overlay

The holder vocabulary names a graph by scope and a holder by
location, scope, and form, so the overlay namespace takes the
holder's name: app.graph.relation-overlay, with its console,
queries, and sync children. Aliases keep their short forms.

schema-version keeps its "penpot-graph-overlay-2" literal: it is
a storage version, and renaming it would rekey stored artefacts
without a schema change.

AI-assisted-by: glm-5.3-flash
Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
This commit is contained in:
Álvaro Tejero Cantero 2026-10-06 21:45:41 +02:00
parent bf1efeff6e
commit d9ef6eb789
No known key found for this signature in database
10 changed files with 56 additions and 56 deletions

View File

@ -71,7 +71,7 @@
;; Required by Arrow RootAllocator (lbug only pulls arrow-memory-core).
org.apache.arrow/arrow-memory-netty {:mvn/version "19.0.0"}
;; graph-overlay: datascript index over the document, replacing the Cypher
;; relation-overlay: datascript index over the document, replacing the Cypher
;; projection for per-file queries. See docs.local/graph roadmap.
datascript/datascript {:mvn/version "1.8.1"}}

View File

@ -8,9 +8,9 @@
"In-memory overlay sessions for the debug graph console.
A session is a datascript overlay of one file
(`app.graph.overlay`), maintained from the msgbus file-change feed by
`app.graph.overlay.sync` and queried in Datalog through
`app.graph.overlay.console`. The Ladybug engine no longer appears on
(`app.graph.relation-overlay`), maintained from the msgbus file-change feed by
`app.graph.relation-overlay.sync` and queried in Datalog through
`app.graph.relation-overlay.console`. The Ladybug engine no longer appears on
this path: it keeps the batch-export tier (`app.graph.ingest`), where
columnar output is the point. The overlay is immutable data in an atom,
so readers never lock and the sync loop is the only writer."
@ -20,10 +20,10 @@
[app.common.logging :as l]
[app.common.time :as ct]
[app.db :as db]
[app.graph.overlay :as overlay]
[app.graph.overlay.console :as console]
[app.graph.overlay.queries :as queries]
[app.graph.overlay.sync :as overlay.sync]
[app.graph.relation-overlay :as overlay]
[app.graph.relation-overlay.console :as console]
[app.graph.relation-overlay.queries :as queries]
[app.graph.relation-overlay.sync :as overlay.sync]
[app.msgbus :as mbus]
[app.srepl.helpers :as h]
[clojure.string :as str]
@ -37,7 +37,7 @@
The `filter_*` columns carry node ids for the graph-view result filter;
the results table hides them (see `hide-filter-columns` and the
template's `renderQueryOutput`). `$` is bound to the overlay and `%` to
the shared rule set of `app.graph.overlay.queries`."
the shared rule set of `app.graph.relation-overlay.queries`."
(str "[:find ?component ?instance ?page\n"
" ?filter_src_id ?filter_tgt_id\n"
" :in $ %\n"
@ -84,7 +84,7 @@
(defn- mark-stale
"Record a staleness verdict in a session's sync state, once.
Every verdict `app.graph.overlay.sync/staleness` returns is a permanent
Every verdict `app.graph.relation-overlay.sync/staleness` returns is a permanent
divergence: the index has folded a state no rebuild produces, and the
batches after it fold onto that state. So a later clean batch must not
erase the mark, and only `load-session!` clears it, by building fresh
@ -162,7 +162,7 @@
(if-let [msgbus (:msgbus session)]
;; A dropping buffer is the silence this subsystem cannot see: a full
;; buffer discards a message with no log line and no revision change.
;; `app.graph.overlay.sync/staleness` catches it one message later,
;; `app.graph.relation-overlay.sync/staleness` catches it one message later,
;; because the next message's revision is then two beyond the
;; index's, and the read gates refuse until a reload rebuilds.
(let [sync-ch (sp/chan :buf (sp/dropping-buffer 64))]
@ -193,7 +193,7 @@
(defn sync-status
"Return incremental sync status for the active session.
`:stale` is the verdict `app.graph.overlay.sync/staleness` returned for
`:stale` is the verdict `app.graph.relation-overlay.sync/staleness` returned for
the batch that broke the index, nil while the index still equals a
rebuild, and `:reason` names which of the three cases it was. The two
revisions are reported beside it and neither is the gate: `:revn` is
@ -268,7 +268,7 @@
(defn- check-fresh!
"Refuse a read from an index that has fallen behind.
The mark is `app.graph.overlay.sync/staleness`'s verdict, recorded by
The mark is `app.graph.relation-overlay.sync/staleness`'s verdict, recorded by
`apply-file-change!`, and a reload clears it by rebuilding
(`load-session!`). This refusal is what makes partial change-type
coverage a performance property rather than a correctness one: a
@ -286,7 +286,7 @@
"Run a Datalog `statement` against the overlay for `profile-id`.
Read-only by construction (`d/q` cannot transact), gated against
function smuggling by `app.graph.overlay.console/check-query!`, and
function smuggling by `app.graph.relation-overlay.console/check-query!`, and
gated against a stale index by `check-fresh!`."
[profile-id statement]
(when (or (nil? statement) (= "" statement))

View File

@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.graph.overlay
(ns app.graph.relation-overlay
"Datascript index over a Penpot document: identity, type, and topology.
The overlay is an index over what Penpot's helpers read, never a
@ -40,7 +40,7 @@
- **Token applications fold the property into the attribute name**:
`{:token/fill \"layerTwo.background\"}` on the shape itself, over the
closed vocabulary of `app.common.types.token/all-keys`. The
`uses-token` rule in `app.graph.overlay.queries` hides the encoding,
`uses-token` rule in `app.graph.relation-overlay.queries` hides the encoding,
so the choice stays reversible.
The namespace is deliberately JVM-free (no imports, no interop): the
@ -81,12 +81,12 @@
Color references are stored per source (`fill`, `stroke`, text content)
rather than as one union, because the sync path receives `:set` ops one
attribute at a time and must be able to rebuild each contribution
independently. The `uses-color` rule in `app.graph.overlay.queries`
independently. The `uses-color` rule in `app.graph.relation-overlay.queries`
reunites them. Each source also keeps the raw id it resolved from, as a
value rather than a ref: the id joins nothing until
`build-asset-maps` finds the asset, and an asset the library does not
hold yet leaves the id stored and the reference absent, which is the
state `app.graph.overlay.sync/apply-add-asset` repairs. Folded token
state `app.graph.relation-overlay.sync/apply-add-asset` repairs. Folded token
attributes (`:token/fill`, ...) are plain string values and need no
declaration."
{;; document
@ -128,7 +128,7 @@
:shape/uses-typography {:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many}
;; the raw ids those four resolved from, one attribute per source,
;; indexed because `app.graph.overlay.sync/apply-add-asset` reads the
;; indexed because `app.graph.relation-overlay.sync/apply-add-asset` reads the
;; shapes waiting on an id
:shape/fill-color-ref-id {:db/index true
:db/cardinality :db.cardinality/many}
@ -311,7 +311,7 @@
The raw id is written whether or not the ctx resolves it. A shape that
names an asset the library does not hold keeps the id and carries no
reference, which is what `app.graph.overlay.sync/apply-add-asset` reads
reference, which is what `app.graph.relation-overlay.sync/apply-add-asset` reads
when the asset finally arrives; a reference is written only for an id
the ctx resolves."
[shape {:keys [colors typographies]}]

View File

@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.graph.overlay.console
(ns app.graph.relation-overlay.console
"Free-form Datalog for the debug graph console: parse, gate, run,
format.
@ -21,7 +21,7 @@
binder's own courtesy: the error names the symbol."
(:require
[app.common.exceptions :as ex]
[app.graph.overlay.queries :as queries]
[app.graph.relation-overlay.queries :as queries]
[clojure.edn :as edn]
[datascript.built-ins :as bi]
[datascript.core :as d]))
@ -137,7 +137,7 @@
"Parse, gate, and run `text` against overlay `db`.
`$` is always supplied; `%` (the shared rule set of
`app.graph.overlay.queries`) is supplied when the query's :in asks for
`app.graph.relation-overlay.queries`) is supplied when the query's :in asks for
it. Returns {:columns [...] :rows [...] :truncated? bool :row-count n}
in the shape the console template renders."
[db text]

View File

@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.graph.overlay.queries
(ns app.graph.relation-overlay.queries
"The relation vocabulary and the standing question set over the overlay.
**The relation vocabulary is a rule set, not raw clauses**
@ -36,7 +36,7 @@
stays the oracle and is never reimplemented here."
(:require
[app.common.uuid :as uuid]
[app.graph.overlay :as overlay]
[app.graph.relation-overlay :as overlay]
[datascript.core :as d]))
(def rules

View File

@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC Sucursal en España SL
(ns app.graph.overlay.sync
(ns app.graph.relation-overlay.sync
"Incremental overlay updates from Penpot change vectors.
The invariant this namespace defends: an overlay maintained from change
@ -50,8 +50,8 @@
[app.common.types.page :as ctp]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[app.graph.overlay :as overlay]
[app.graph.overlay.queries :as queries]
[app.graph.relation-overlay :as overlay]
[app.graph.relation-overlay.queries :as queries]
[datascript.core :as d]))
(def supported-change-types
@ -77,7 +77,7 @@
(some-> (first (d/datoms db :avet :document/id)) :e))
(defn- sync-asset-ctx
"The asset half of `app.graph.overlay`'s build-ctx, resolved against the
"The asset half of `app.graph.relation-overlay`'s build-ctx, resolved against the
live db: values are eids rather than tempids."
[db]
{:colors (into {} (map (fn [dtm] [(:v dtm) (:e dtm)]))
@ -860,7 +860,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; library assets: colours and typographies
;;
;; The entity is the one `app.graph.overlay/build-tx` writes for a member
;; The entity is the one `app.graph.relation-overlay/build-tx` writes for a member
;; of `:colors` or `:typographies`: identity, the document ref, and the
;; name a standing query asks for. The value lives in the document,
;; which the holder of the overlay already has.
@ -952,7 +952,7 @@
of `ctyl/delete-typography`: both dissoc the asset from the library map
and leave every referring shape alone. A rebuild therefore loses the
asset entity *and* every resolved reference to it, because
`app.graph.overlay/build-asset-maps` only maps the ids the library
`app.graph.relation-overlay/build-asset-maps` only maps the ids the library
still holds.
One retraction produces exactly that. Datascript's
@ -971,7 +971,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; design tokens
;;
;; The entity shape is `app.graph.overlay/tokens-tx`'s: one entity per
;; The entity shape is `app.graph.relation-overlay/tokens-tx`'s: one entity per
;; token set (identity, name, document ref) and one per token (identity,
;; name, type, set ref). Values, themes and resolution stay in the lib,
;; which `app.common.types.tokens-lib` already answers.
@ -1016,7 +1016,7 @@
(defn- token-entities
"Token entities of one set from a lib-shaped token map, in
`app.graph.overlay/tokens-tx`'s shape."
`app.graph.relation-overlay/tokens-tx`'s shape."
[set-ref tokens]
(into []
(map (fn [token]
@ -1082,7 +1082,7 @@
and token entity goes, and the incoming library's sets and tokens are
written fresh. Nothing outside the pair points at them, because an
applied token is folded onto the shape as a plain name
(`app.graph.overlay/token-attrs`), so no shape datom needs repair."
(`app.graph.relation-overlay/token-attrs`), so no shape datom needs repair."
[db {:keys [tokens-lib]}]
(let [old (-> (mapv (fn [dtm] [:db.fn/retractEntity (:e dtm)])
(d/datoms db :avet :token-set/id))

View File

@ -6,7 +6,7 @@
(ns backend-tests.graph-overlay-console-test
"Pure tests over the console's parse-gate-run pipeline
(`app.graph.overlay.console/run-query`) and the default query it ships
(`app.graph.relation-overlay.console/run-query`) and the default query it ships
(`app.graph.debug/default-query`).
The gate exists so the console cannot run arbitrary Clojure; these
@ -21,8 +21,8 @@
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.graph.debug :as debug]
[app.graph.overlay :as overlay]
[app.graph.overlay.console :as console]
[app.graph.relation-overlay :as overlay]
[app.graph.relation-overlay.console :as console]
[clojure.string :as str]
[clojure.test :as t]
[datascript.core :as d]))
@ -80,7 +80,7 @@
(t/deftest shared-rule-invocations-pass-the-gate
;; The gate's allowlist derives from `queries/rules`
;; (`app.graph.overlay.console/check-query!`), so rule renames pass
;; (`app.graph.relation-overlay.console/check-query!`), so rule renames pass
;; automatically: the default query proves `instance-of`, and these
;; hold the door open for `descendant-of` (with rows) and `uses-token`
;; (the fixture defines no tokens, so the gate acceptance is the

View File

@ -8,8 +8,8 @@
"Cold build and incremental sync are two implementations of one mapping,
and this namespace holds them to it.
`app.graph.overlay/build` projects a whole file into a fresh overlay.
`app.graph.overlay.sync/apply-changes` replays the change vocabulary the
`app.graph.relation-overlay/build` projects a whole file into a fresh overlay.
`app.graph.relation-overlay.sync/apply-changes` replays the change vocabulary the
editor emits onto an overlay that is already open. The overlay the second
one maintains must equal the overlay the first one would build from the
changed document, or the console shows a graph no rebuild reproduces.
@ -31,9 +31,9 @@
[app.common.types.typography :as ctt]
[app.common.uuid :as uuid]
[app.graph.debug :as graph.debug]
[app.graph.overlay :as overlay]
[app.graph.overlay.queries :as queries]
[app.graph.overlay.sync :as sync]
[app.graph.relation-overlay :as overlay]
[app.graph.relation-overlay.queries :as queries]
[app.graph.relation-overlay.sync :as sync]
[clojure.set :as set]
[clojure.string :as str]
[clojure.test :as t]
@ -135,7 +135,7 @@
an editing session would emit them, and every indexed attribute at least
once: topology (add, reparent, reorder, delete a subtree), the
`:mod-obj` set ops
with their asset refs (`app.graph.overlay.sync/set-attr-tx`), a touched
with their asset refs (`app.graph.relation-overlay.sync/set-attr-tx`), a touched
swap slot on a shape inside a copy (`ctk/get-swap-slot`), a copy
child's `:shape-ref` repointed at another main shape (the resolved
`:shape/refers-to` must follow), the whole component lifecycle from add
@ -207,7 +207,7 @@
;; the frame's children are sorted by the index each named id holds
;; here, and rect-id, which the change does not name, keeps the -1 key
;; and stays first. The sibling ordinals must follow that sort
;; (`app.graph.overlay.sync/apply-reorder-children`).
;; (`app.graph.relation-overlay.sync/apply-reorder-children`).
{:type :reorder-children :page-id page-id :parent-id frame-id
:shapes [circ-id rect2-id]}
@ -686,11 +686,11 @@
;; The third filter, after the change types and the operations: the
;; attributes a shape entity carries. They do not have one writer each. An
;; operation writes `:shape/name` and its neighbours through
;; `app.graph.overlay.sync/set-attr-tx`, a structural pass writes
;; `app.graph.relation-overlay.sync/set-attr-tx`, a structural pass writes
;; `:shape/refers-to` (`reresolve-refs-tx`) and the sibling ordinal
;; (`order-tx`, which is also how the builder draws it), and the builder
;; writes the identity and topology edges and `:shape/type`. The comparison
;; asks whether every attribute `app.graph.overlay/schema` declares over
;; asks whether every attribute `app.graph.relation-overlay/schema` declares over
;; shape entities, plus the folded token vocabulary, has a writer, and
;; names its kind. An attribute nothing can write fails by name rather than passing
;; as derived, which is the failure mode the operation filter had before
@ -748,7 +748,7 @@
:shape/exit "the fold and the rebuild number the intervals differently; containment is the invariant"})
(defn- declared-shape-attrs
"The attributes `app.graph.overlay/schema` declares over shape entities,
"The attributes `app.graph.relation-overlay/schema` declares over shape entities,
plus the folded token vocabulary that needs no declaration."
[]
(into (into #{} (filter (fn [attr] (= "shape" (namespace attr)))) (keys overlay/schema))
@ -966,7 +966,7 @@
;; The Euler-tour invariants hold on the synced db, not only on the
;; built one. The sync path renumbers a container from beyond the
;; global maximum with a different DFS order than the builder
;; (`app.graph.overlay.sync/renumber-container-tx`), so interval
;; (`app.graph.relation-overlay.sync/renumber-container-tx`), so interval
;; values are not comparable between the two paths — containment is.
;; Here: every shape of every container carries an interval, the
;; interval form answers the recursive walk exactly, and intervals are

View File

@ -13,7 +13,7 @@
that is the correctness oracle. A backported assertion that disagrees
with a helper is a beadpot bug: the helper's answer is asserted and the
divergence is noted in the docstring. Beadpot's idempotency tests have
no port here: `app.graph.overlay/build` is a pure function of the
no port here: `app.graph.relation-overlay/build` is a pure function of the
document, so linking twice cannot duplicate an edge by construction.
The fixture is built exclusively with the common test helpers
@ -35,8 +35,8 @@
[app.common.types.text :as txt]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[app.graph.overlay :as overlay]
[app.graph.overlay.queries :as queries]
[app.graph.relation-overlay :as overlay]
[app.graph.relation-overlay.queries :as queries]
[clojure.test :as t]
[datascript.built-ins :as bi]
[datascript.core :as d]))
@ -89,7 +89,7 @@
(defn- oracle-containers
"The builder's resolution containers, one per page plus one per
component that carries its own `:objects` snapshot, as [ctn-container
container-id] pairs (`app.graph.overlay/ref-resolver`)."
container-id] pairs (`app.graph.relation-overlay/ref-resolver`)."
[data]
(concat (map (fn [[id page]] [(ctn/make-container page :page) id])
(:pages-index data))
@ -520,7 +520,7 @@
;; (1) the interval form answers the recursive form, and both equal
;; `cfh/get-children-ids` (the oracle) for every frame of every
;; container; (2) one global counter numbers every container
;; (`app.graph.overlay/euler-numbering` threads it file-wide), so
;; (`app.graph.relation-overlay/euler-numbering` threads it file-wide), so
;; [enter exit] intervals across the whole file are pairwise
;; disjoint-or-nested — a partial overlap is the signature of the
;; per-container-counter bug the 20260820 report guards against.
@ -742,7 +742,7 @@
(def ^:private control-symbols
"Clause heads that structure a query rather than call a function, the
console gate's own list (`app.graph.overlay.console`)."
console gate's own list (`app.graph.relation-overlay.console`)."
'#{not not-join or or-join and pull})
(def ^:private rule-names

View File

@ -767,7 +767,7 @@
destination's chain up `:parent-id`, reading `:component-id` and
`:shape-ref` off every shape it meets: a caller holding those four facts
per shape can ask this without a document. It is public because
`app.graph.overlay.sync` is that caller, and the incremental overlay
`app.graph.relation-overlay.sync` is that caller, and the incremental overlay
refuses a move the document refuses by asking this predicate rather than
by answering the same question a second time."
[objects shape-id parent-id {:keys [allow-altering-copies syncing]}]