area_thesaurus
The back-office area that renders and edits the hierarchical thesaurus trees (taxonomies under the
hierarchyTLD and project thesauri), and the host of the client tree widget.See also: area · area_ontology · TS tree (ts_object) · Sections · Components
This page assumes you already know what an area is — a top-level menu
grouping that is an ontology node, owns no records of its own, and groups
sections. The tree machinery itself (node builder, mutations API, client widget)
is documented in TS tree; this page covers only how
area_thesaurus drives that machinery.
Role
area_thesaurus presents every active thesaurus as an expandable tree in one
screen. It resolves which hierarchies are active, groups them by typology,
supplies the root terms each tree starts from, and powers thesaurus search with
full ancestor-path resolution.
The tree rows themselves are not built here: each row is a section rendered as
a ts_object node, and every mutation (add child,
reparent, save order) belongs to that subsystem. area_thesaurus is the host.
In the area behavior taxonomy (src/core/concepts/area.ts) it carries the
tree behavior, which it shares with area_ontology — the
same area pointed at the ontology hierarchy instead. The two are one
implementation, selected by an argument.
Where the engine lives
| module | what it does |
|---|---|
src/core/area/tree.ts — readAreaHierarchyData |
The boot payload: the active hierarchies, their root terms and children tipos, and the typology list. |
src/core/area/read.ts — readTreeArea |
The tree behavior branch: the per-hierarchy permission filter, the structure context (with the section_tipo override and the thesaurus_mode stamp), and the optional pre-executed search. |
src/core/ts_object/search.ts — searchThesaurus, getHierarchyTermsSqo |
Search with path resolution, and the SQO that scopes a search to chosen hierarchy terms. |
src/core/ts_object/ |
The tree rows themselves — node assembly, expand, move, add, order. Shared with the ontology area. |
Responsibilities
- Active-hierarchy resolution — determine which hierarchies are live, skip
those that are not
active_in_thesaurus, or have no typology, no root terms or nochildren_tipo. - Typology grouping — resolve each hierarchy's typology id, label and order so the client can group the trees under collapsible typology blocks.
- Root-term supply — provide the root-term locators each tree starts from,
and the
children_tipothe client needs to expand each root. - Permission filtering — drop every hierarchy the reader may not read, and every root term whose section they may not read.
- Thesaurus search with path resolution — run an SQO and, for every hit, walk its ancestors and emit the tree data for the whole branch, so the client can rebuild and highlight the path to each result.
It persists nothing: like every area it holds no records.
The storage model it reads
A thesaurus is a hierarchy of section records linked by parent/child locators.
The child stores the parent reference in its relation column under the
component_relation_parent tipo (dd47); children are always computed by
searching who points at a parent (component_relation_children).
area_thesaurus only reads this graph — to find root terms, to iterate children
when building search paths, and to walk ancestors. The authoritative description
lives in TS tree and the
component_relation_parent /
component_relation_children
references.
The boot payload
readAreaHierarchyData(model, areaTipo, lang, termsAreModel) turns each active
hierarchy element into one tree root group. The fields it stamps:
| field | meaning |
|---|---|
section_id, section_tipo |
the hierarchy definition record (in hierarchy1, or ontology35 for the ontology area) |
target_section_tipo |
the section whose records are the tree terms |
target_section_name |
the hierarchy element's name |
children_tipo |
the component_relation_children tipo the client uses to expand a node |
typology_section_id |
the typology this hierarchy belongs to |
order |
the hierarchy's order |
type |
always 'hierarchy' |
active_in_thesaurus |
the element's thesaurus-active flag |
root_terms |
the root-term locators the tree starts from |
readTreeArea then wraps it: it filters the hierarchies by read permission,
rebuilds the typology list from the survivors, builds the structure context, and
attaches ts_search when a search was requested.
What the client receives
{
"tipo": "dd100",
"value": [
{
"section_id": 1,
"section_tipo": "hierarchy1",
"target_section_tipo": "es1",
"target_section_name": "Onomastic places (Spain)",
"children_tipo": "hierarchy49",
"typology_section_id": 7,
"order": 3,
"type": "hierarchy",
"active_in_thesaurus": true,
"root_terms": [ { "section_tipo": "es1", "section_id": 1 } ]
}
],
"typologies": [
{ "section_id": 7, "type": "typology", "label": "Geographic", "order": 3 }
],
"ts_search": { "result": [], "found": [], "total": 0 }
}
ts_search is present only when a search — or the area's pinned
properties.hierarchy_terms — was requested.
Illustrative tipos
The tipo / section_tipo / children_tipo values above are examples; the
real values are installation-specific ontology tipos. The structure — field
names and nesting — is the contract.
The context: two stamps the client needs
readTreeArea builds the area's structure context and then stamps two things
onto it:
section_tipois set to the area tipo, so the search panel can store per-area presets against it;thesaurus_modesays whether this read is an ordinary browse (default) or a term picker (relation). It is derived by the server — see below.
thesaurus_mode is server-derived, not requested
The mode is never taken from the request. A read that names no caller is a browse
and is stamped from the area node's own properties.thesaurus_mode (defaulting to
default), which is the install default for that area page. A read that names a
caller is stamped with the mode the server derived from that caller, and the area
property does not enter into it: a picker request cannot be handed a mode its caller
did not earn.
A mode the client declares is a mode anyone can declare
Accepting thesaurus_mode from the request would let any read open relation mode
over any thesaurus, with no component that ever asked to pick terms. The request
names an address (the caller) that the server re-resolves; it never names a
conclusion the server adopts.
The term picker (relation mode)
A relation component whose ontology declares properties.view: "tree" opens its
target thesaurus as a picker: the same tree read, rendering a link affordance per
term instead of navigating to it. The picker is not a second area — it is this read,
with a caller.
The request declares the caller. source.caller carries
{section_tipo, section_id, tipo} — the record and component the picked terms will be
linked into. A malformed caller, a caller naming an element the ontology does not
define, and a caller whose section holds no records are each refused with their own
named 400.
The server derives the rest, and grants relation mode only when all three hold:
the caller's resolved view is tree, its model stores relations, and the user has
edit permission on it. Otherwise the read proceeds in default mode.
In relation mode the response carries three extra facts, all absent from a browse read:
| Field | Where | What |
|---|---|---|
thesaurus_mode |
context[0] |
"relation" |
picker |
context[0] |
{selection_limit, remaining, targets} — the caller's cap and the sections it may link into |
root_terms_selectable |
each hierarchy item in data[0].value |
[{section_tipo, section_id, selectable}], one entry per root term |
selection_limitis the caller'sdata_limit:nullmeans uncapped, and a literal0means nothing may be linked.remainingis that limit minus the locators the caller already holds. It — not the limit — is what one picker session may add: a component withdata_limit: 2already holding one term accepts exactly one more pick. When it reaches0the tree still browses, and says why, instead of offering a link that would be refused.targetsnarrows the hierarchies the read returns to the caller's own target sections (virtual and real sections are matched against each other, so a virtual target still finds its hierarchy).selectableis the term's own answer, the same flag the indexation tool has always used: a term is linkable when the thesaurus says so, term by term. A term that is not selectable stays visible and navigable — those are the levels one passes through to reach a leaf — and simply carries no link affordance.
Where a row's selectable comes from
A picker does not only paint trees. A search result, a section list opened as a picker (the indexation tool browses people that way) and a tree node are all rows that need the same answer, and the client reads it from whichever of three server channels answers — it never computes one:
| Channel | Carried by | Answers for |
|---|---|---|
root_terms_selectable |
the area read, keyed by locator | root terms of each hierarchy |
is_indexable |
each tree node, and each section-list envelope entry | any record whose section declares the is_indexable role |
selectability_declared: false |
each section-list envelope entry | every record of a section that declares no such role |
The third channel exists because silence is not an answer. A section with no
is_indexable role has no per-term contract, and saving a pick into it is already
exempt from that check — so its rows must be pickable. While the read said nothing at
all for those rows, the client could not tell "no contract" from "nobody answered" and
withheld the affordance from records the save would have accepted.
The two envelope keys are mutually exclusive: a section that declares the role answers
per row with is_indexable and never sends the exemption. Only a declaring section
that fails to answer for a row leaves the row genuinely unknown, and an unknown row gets
no affordance — an affordance is never offered on a guess, and never withheld on one.
See is_indexable in the section map documentation for what declaring the role costs:
The is_indexable contract.
Two empty pickers are two different facts, and the read distinguishes them: everything
refused by permissions answers 403 with a message that names nothing (naming what you
may not see is itself a leak), while a target that declares no active hierarchy — or
none among this caller's targets — answers 409 read: no active hierarchy is configured
for this component target, which is a configuration problem, not a permission one.
The affordance is not the authorization
The same cap and the same target set are resolved again when the pick is saved, by
the same module. What the tree offers and what the save accepts are literally the
same value, so a stale or altered client cap changes nothing. A pick the save
refuses — a term outside the caller's targets, a non-selectable term, one past the
cap — is answered as a named error (relation.insert_refused, HTTP 400), and a
thesaurus the picker's user may not read answers the generic permission error
(HTTP 403); a refused pick is never a silent no-op.
The context must never be empty
An area read that returns an empty context makes the client render the area
blank. Every area read returns a non-empty context; the tree client guards
on it explicitly.
Search with path resolution
searchThesaurus(sqo, principal) (src/core/ts_object/search.ts) does three
things:
- Sanitizes the untrusted client SQO and builds the SQL through the shared search chokepoint, so the principal's project filter and permissions apply.
- For every hit, walks its ancestors (
getParentsRecursive,src/core/relations/parent.ts), memoized per call, and reverses the chain so the root comes first. - For every node on every path, builds the tree data for the whole branch —
root, each ancestor, and that ancestor's children — batch-resolving the
children's indexable flag with one query for the whole child set rather than
one per child. Nodes are keyed by
section_tipo:section_id, so a branch shared by two hits is emitted once.
It returns { result, msg, errors, total, found }: found is the raw hit
locators, result is the tree data covering every branch that leads to one.
getHierarchyTermsSqo(hierarchyTerms) builds the SQO that scopes a search to a
chosen set of {section_tipo, section_id} terms — used to seed a search from the
area's pinned properties.hierarchy_terms.
A search reaches the area two ways: the client sends
source.search_action = 'search' together with an rqo.sqo, or the area node
carries properties.hierarchy_terms. Either way the request-specific values are
never baked into the shared structure-context cache.
How it fits with the rest of Dédalo
- TS tree (
ts_object/dd_ts_api) — the area is the host; every tree row is ats_object. The area supplies roots and search paths; expand/collapse, children resolution, term rendering and all mutations live in the tree subsystem. Build ats_objectfrom a node; never reimplement node rendering here. - area_ontology — the same area in ontology mode.
- hierarchy — the active-element registry and the root-term / main-order source.
- area — the area family reference: behavior taxonomy, menu walk, write refusal.
- Sections — each tree term is a record of a section.
- Components — the tree leans on
component_relation_parentandcomponent_relation_childrenfor its edges,component_selectfor the typology, and the text components for labels. - Search (SQO) — the query format
searchThesaurusconsumes.
Related
- TS tree (ts_object) — the node builder, the mutations API and the client tree widget the area hosts.
- area — the area reference.
- area_ontology — the same machinery in ontology mode.
- hierarchy — active elements, root terms, main order.
- component_relation_parent · component_relation_children — the parent/child edges of the tree.
- Sections — the records that are the tree terms.
- SQO — the query format used by thesaurus search.
- Architecture overview — areas → sections → components → data.