inspector
The client-side side panel of the section edit view — a context-aware dashboard that shows record/component metadata, tools, projects, relations, time-machine history and save activity for the section currently being edited.
See also: Sections · Components · dd_object · Events
This page is the subsystem reference for the inspector. The inspector is a
pure client (JavaScript + LESS) subsystem — it has no server-side
counterpart. It lives in client/dedalo/core/inspector/ and is driven
entirely by the data its caller section already holds (context, data,
tools) plus a handful of small API calls. Everything it displays is
resolved server-side by the
section and tool
machinery; the inspector only renders and reacts.
Role
The inspector is the right-hand panel of the section edit view (mode==='edit').
It is created by the section instance and rendered into #inspector_container
by view_default_edit_section.js. Conceptually it is the section's "developer +
curator console": for the active record it surfaces identity (tipo, model,
matrix table, section_id), creation/modification/publication metadata, the
record's project assignment, its inbound relations, recent change history and a
live activity feed; for the currently focused component it swaps that record
view for component-level info (tipo, model, translatable flag, current value,
per-component history).
It is a plain constructor function (inspector) whose prototype is patched
with a few methods borrowed from the shared client common (render,
refresh) plus its own edit/init/build/destroy/get_raw_record*. It
sits next to these neighbouring client subsystems:
| subsystem | relationship |
|---|---|
section (client/dedalo/core/section/js/section.js) |
The inspector's caller. The section constructs the inspector in mode==='edit' with permissions, owns its lifecycle, and is the source of all context/data/tools/rqo the inspector reads. |
view_default_edit_section (client/dedalo/core/section/js/view_default_edit_section.js) |
Builds #inspector_container, renders the inspector wrapper into it, attaches the resize handle, applies the persisted rail/width state, and moves the section paginator into the inspector. |
| service_time_machine (Time Machine) | Instanced on demand by the inspector for the section "Latest changes" list and the per-component history. The inspector never reads dd15 history itself; it delegates to the service. |
| open_tool | Every tool button in the inspector calls open_tool(); the inspector only decides which tools to show (show_in_inspector) and renders their buttons. |
| relation_list | Instanced on demand to render the "Relations" block. |
No server counterpart
The inspector itself has no server-side object. The data it shows is
produced by ordinary section/component reads, the tools registry
(including the show_in_inspector flag — see below), and the Time
Machine read path. The client creates the inspector whenever a section
renders in mode==='edit' with sufficient permissions — there is no
separate server-side toggle for it.
Responsibilities
- Own the panel lifecycle —
init(wire events),build(no-op, kept for lifecycle parity),edit/render(build the DOM),destroy(unsubscribe). - Render the record dashboard — the paginator, the top button bar (search / new / duplicate / delete / open-relationships / graph / diffusion), the inspector tool buttons, selection info, element-info block, project block, relation list, time-machine list, component history, activity feed and the bottom "view record data" / register-download buttons.
- React to the active component — on the
activate_componentevent swap the element-info block from section info to component info, load that component's history, and (in developer mode) re-point an open ontology window. - React to saves — on the
saveevent refresh the selected component's history, refresh the section time-machine list, and push an activity bubble. - Manage panel chrome — rail collapse/expand, live drag-resize, and persist
both states (rail + width) in the IndexedDB
statustable as global keys. - Fetch the raw record — build a
read_rawRQO and request the full database record for the "view record data" link and thedd1340register download. - Resolve ontology documentation links — build docu / local-ontology / master-ontology URLs per tipo and open them in a recycled window.
- Worker/instance hygiene — keep its event tokens and on-demand service
instances so
destroycan tear them down cleanly.
Key concepts
The caller is the single source of truth
The inspector holds self.caller — the section instance. Almost everything
it renders is read straight off the caller, not re-fetched:
self.caller.context.tools— filtered byshow_in_inspectorto decide which tool buttons appear.self.caller.context.buttons— to detectbutton_new/button_delete.self.caller.context.config.relation_list_tipo— present ⇒ render the Relations block.self.caller.data.entries[0]— the record metadata (created/modified/ published dates and users).self.caller.tools— to detecttool_diffusion(the dedicated diffusion button) and to open tools viaopen_tool.self.caller.section_tipo/section_id/model/label/mode/view/rqo— identity, paginator and theread_rawRQO.
The inspector only issues its own API calls for two things: the raw-record
fetch (get_raw_record) and the on-demand service_time_machine /
relation_list instances.
Section view vs component view
The element-info block (the "Info" card) is dual-mode and shares one body
node, self.element_info_container:
- Section view —
render_section_info()fills it with the record's tipo, docu links, model, matrix table, section_id, view+mode, created, modified and published metadata. Shown when no component is active. - Component view —
render_component_info()fills it with the focused component's tipo, docu links, model, translatable flag, view+mode and a collapsible JSON dump of the component's currentdata.entries(with a copy button). It subscribes toupdate_value_<component.id_base>so the value re-renders live as the user edits, and unsubscribes the previous token each time the selection changes.
The switch is event-driven: activate_component → component view;
deactivate_component (debounced 250 ms, only if no component is active) and
render_<section.id> → section view.
Lazy, collapsible blocks
Each card is a header + body wired through ui.collapse_toggle_track, with the
collapsed/expanded state persisted under a stable collapsed_id
(inspector_main_block, inspector_element_info_block, inspector_project_block,
inspector_relation_list, inspector_time_machine_list,
inspector_component_history_block, inspector_activity_info,
inspector_component_value). The heavy blocks (relation list, time-machine
list, component history) default to closed and only fetch their data in the
expose callback (lazy load), guarding every async load with a
"still open / not destroyed" check.
Persisted panel state (rail + width)
Two global IndexedDB status keys persist across sections and reloads:
| key | meaning | default |
|---|---|---|
inspector_rail_state |
true ⇒ collapsed to the icon rail |
not collapsed |
inspector_width |
user-chosen panel width (e.g. '24rem') |
'19rem' (DEFAULT_WIDTH) |
Width is applied by writing the CSS custom property --inspector_width on
:root; the section content and the panel position are calc() consumers of
that var, so the layout reflows automatically. Rail mode and resize are inert
below the narrow breakpoint (max-width: 1024px), where the panel is inline and
full-width.
Files & structure
client/dedalo/core/inspector/
├── js/
│ ├── inspector.js # the instance: lifecycle, events, RQO helpers, ontology URLs
│ └── render_inspector.js # the DOM: edit() builder + every block renderer/loader
└── css/
├── inspector.less # panel + card styling (included from main.less)
└── inspector.css.map
inspector.jsis the entry: it exports theinspectorconstructor and patches its prototype. It ownsinit(event wiring),build,destroy,get_raw_record/get_raw_record_rqo, the module-levelupdate_section_info()helper, and the exportedget_ontology_url().inspector.prototype.editis borrowed fromrender_inspector.prototype.edit.render_inspector.jsis the view layer:edit()assembles the wrapper andget_content_data()builds every card. It also exports the chrome helpers (toggle_inspector_rail,apply_inspector_state,init_inspector_resize) and the per-block render/load functions consumed byinspector.js's event handlers.
DOM shape
#inspector_container (built by view_default_edit_section.js)
├── .inspector_resize_handle (left-edge drag handle)
└── #inspector .inspector (the wrapper returned by edit())
├── .label .icon_arrow (collapsible header + rail toggle)
└── .content_data.inspector_content_data
├── .paginator_container (section paginator moved in here + .section_id)
├── .buttons_container.top (search / new / duplicate / delete / open-rel / graph / diffusion)
├── .tools_container.top (inspector tool buttons, when >1)
├── .selection_info (current element label + List button)
├── .element_info_wrap (the dual-mode "Info" card → .element_info)
├── .project_wrap (component_filter, when relation_list config present)
├── .relation_list_container (when context.config.relation_list_tipo)
├── .time_machine_list ("Latest changes", section-level)
├── .component_history (per active component)
├── .activity_info (save/error bubbles)
└── .buttons_container.bottom (view record data + dd1340 register download)
Instantiation & lifecycle
The inspector is not built through the get_instance() factory. The section
instantiates it directly with new inspector() when it renders in edit mode and
the user has permissions (client/dedalo/core/section/js/section.js):
// in section build, mode 'edit' only
if (self.inspector==null && self.mode==='edit' && self.permissions) {
const current_inspector = new inspector()
current_inspector.init({
section_tipo : self.section_tipo,
section_id : self.section_id,
caller : self // the section instance
})
self.inspector = current_inspector
}
init(options) sets self.caller (the section), self.id =
'inspector_' + section_tipo, self.mode = 'edit', the placeholder node
pointers (paginator_container, element_info_container,
component_history_container, …) and subscribes the event handlers (below). It
has a double-init guard (is_init) and tracks self.status
(initializing → initialized → building → built → destroyed).
Then the view builds and renders it (client/dedalo/core/section/js/view_default_edit_section.js):
const inspector_container = ui.create_dom_element({ id:'inspector_container', /* … */ })
await self.inspector.build() // no-op, lifecycle parity
const inspector_wrapper = await self.inspector.render() // → edit()
inspector_container.appendChild(inspector_wrapper)
apply_inspector_state(self.inspector, inspector_container) // rail/width before paint (no flicker)
// + resize handle and the section paginator moved into inspector.paginator_container
Setting inspector: false suppresses the panel
The section accepts an inspector option. A section built with
inspector: false (e.g. notes/modal cases) skips the panel entirely; the
view then adds the no_inspector class to the section wrapper. When the
option is null/undefined the section creates the inspector itself under the
edit-mode + permissions guard above.
Events the inspector subscribes to (in init)
| event | handler effect |
|---|---|
render_<caller.id> |
update_section_info() — reset active component, re-render section info, reload the time-machine list, clear component history, sync the paginator section_id. |
activate_component |
Store self.actived_component, update selection label, render_component_info(), load that component's history; in developer mode re-point an open ontology window. |
save |
If the saved instance is the active component, reload its history; always reload the section time-machine list and push an activity_info bubble. |
deactivate_component |
After a 250 ms debounce, if no component is active, update_section_info() (back to the section view). |
render_component_filter_<section_tipo> |
Capture the rendered component_filter node (published by render_edit_section_record) and refresh the project block body. |
Teardown
destroy(delete_self=true, delete_dependencies=false, remove_dom=false)
overrides common.destroy: it unsubscribes the live
update_value_* token stored on element_info_container, then delegates to
common.prototype.destroy. (Event tokens collected in self.events_tokens and
the on-demand service_time_machine instance are cleaned up through the common
destroy / service destroy paths.)
Key methods
Grouped by concern. The inspector spans two files; file marks where each lives.
Functions exported from render_inspector.js are consumed by inspector.js's
event handlers and by the section view.
Lifecycle & instance (inspector.js)
| method | static? | purpose |
|---|---|---|
inspector.prototype.init(options) |
Wire identity, node pointers and all event subscriptions; double-init guarded. | |
inspector.prototype.build() |
No-op kept for lifecycle parity (status → built). |
|
inspector.prototype.edit(options) |
(borrowed from render_inspector) Build the wrapper: label + rail toggle + content_data. render_level:'content' returns just the content node. |
|
inspector.prototype.render / refresh |
Borrowed from the client common prototype. |
|
inspector.prototype.destroy(delete_self, delete_dependencies, remove_dom) |
Unsubscribe the live value-update token, then call common.destroy. |
Raw record & RQO (inspector.js)
| method | static? | purpose |
|---|---|---|
inspector.prototype.get_raw_record() |
Build the RQO, call data_manager.request, return the first result (or false). |
|
inspector.prototype.get_raw_record_rqo() |
Build a read_raw RQO filtered by the caller's section_tipo/section_id (limit 1, filter_by_locators). Used by both the "view record data" link and the dd1340 register download. |
|
get_ontology_url(tipo, target) |
export | Resolve a documentation/ontology URL for a tipo. target ∈ docu_link | local_ontology | local_ontology_search | master_ontology. |
Panel chrome (render_inspector.js)
| function | export? | purpose |
|---|---|---|
toggle_inspector_rail(self) |
export | Collapse to / expand from the icon rail, reflow via --inspector_width, persist inspector_rail_state. Inert on narrow viewports. |
apply_inspector_state(inspector, container) |
export | Apply the persisted rail/width to the fresh container before attach (no flicker). |
init_inspector_resize(handle, inspector) |
export | Wire the left-edge drag handle: rAF-coalesced live resize, clamped (14rem … min(60vw, 40rem)), persist inspector_width on release. Disabled while railed/narrow. |
decorate_block_header(header, icon_class, label_html) |
Add the leading mask icon + text-label wrapper to a card header (icons: info|gear|link|history|note|activity|panel). |
DOM builders (render_inspector.js)
| function | export? | purpose |
|---|---|---|
edit(options) |
export (prototype) | Top-level builder: label, rail toggle, collapse tracking, content_data. |
get_content_data(self) |
Build the whole content: paginator, top button bar, inspector tool buttons, and append every card block. | |
render_selection_info(self) |
The orange "current element" label card (+ List nav button via update_label). |
|
render_element_info(self) |
The collapsible "Info" card shell; sets self.element_info_container. |
|
render_section_info(self) |
export | Fill Info with section metadata (tipo, model, matrix_table, created/modified/published, docu links). |
render_component_info(self, component) |
export | Fill Info with the active component (tipo, model, translatable, live JSON value + copy); subscribes update_value_<id_base>. |
render_project_block(self) |
The "Project" card wrapping the section's component_filter node. |
|
update_project_container_body(self) |
export | Swap the project card body to the current filter node. |
render_relation_list(self) |
The "Relations" card (lazy); built only when context.config.relation_list_tipo exists. |
|
render_time_machine_list(self) |
export | The section-level "Latest changes" card shell (lazy, default closed). |
render_component_history(self) |
The per-component "Component history" card shell (lazy, default closed). | |
render_activity_info(self) |
The "Activity" card shell (save/error bubbles). | |
render_docu_links(self, tipo) |
Build the docu-link buttons; the developer-only local/tree/master ontology buttons appear when SHOW_DEVELOPER===true. |
On-demand data loaders (render_inspector.js)
| function | export? | purpose |
|---|---|---|
load_time_machine_list(self) |
export | Serializing entry point. Chains each load onto the previous one through self.tm_list_loading, then delegates to load_time_machine_list_run. |
load_time_machine_list_run(self) |
The actual load: instance a dd15 section (mode list, tm_view mini) for the current record and render it into the Latest-changes body; skips when collapsed/destroyed, destroys the previous tm_list. |
|
load_component_history(self, component) |
export | Instance a service_time_machine (view history) for the active component + its notes (rsc329/rsc832) and render it; clears when component is null. |
load_activity_info(self, options) |
export | Render one notification bubble (render_node_info) and prepend it to the activity body. |
open_ontology_window(self, url, docu_type, focus=false) |
export | Open/recycle the shared window.docu_window at the given ontology URL. |
load_time_machine_list must stay serialized
Three independent triggers call it — the block's expose_callback, the save
handler, and update_section_info — and they can fire in the same frame.
Run in parallel, the second call's self.tm_list.destroy(true, true) nullifies
caller on every section_record the first call is still rendering (see
common.js do_delete_self), and the row render throws
Cannot read properties of null (reading 'model'), leaving the panel stuck on
loading. Never call load_time_machine_list_run directly, and never move the
destroy up into the entry point.
Gate: test/unit/client_tm_list_destroy_race.test.ts.
Verified surface only
The tables above list the real functions present in client/dedalo/core/inspector/js/.
Module-internal helpers (set_inspector_width, is_narrow_viewport,
update_section_info) are not exported and are listed only where they shape
behaviour. No method names here are invented.
How it fits with the rest of Dédalo
The inspector is a thin client renderer over data other subsystems resolve. The load-bearing contracts:
show_in_inspector — how tools opt into the panel
The inspector's tool buttons are not hardcoded. get_content_data() filters the
caller's tool contexts:
const inspector_tools = self.caller.context.tools.filter( el => el.show_in_inspector )
That show_in_inspector flag is a server-resolved property of every tool's
context: a plain boolean field on the resolved tool context
(src/core/tools/types.ts), read off the tool's registration
(SHOW_IN_INSPECTOR in src/core/tools/ontology_map.ts, resolved by
src/core/tools/registry.ts / register.ts). A tool authored with
show_in_inspector: true in its register.json therefore appears as an
inspector button with no client change.
The companion flag show_in_component governs whether the same tool renders in a
component's own toolbar instead. See the tool context example in
Components → Datum/context and the
tools subsystem.
Two diffusion paths
The dedicated Diffusion top button is rendered only when the caller has a
tool_diffusion in self.caller.tools; other tools reach the panel through
the generic show_in_inspector filter. Both ultimately call
open_tool({ tool_context, caller }) from tool_common.
Time Machine
The "Latest changes" and "Component history" cards are rendered by instancing
service_time_machine (model service_time_machine) on demand and calling its
build() / render(). The inspector never queries dd15 history directly. The
component-history loader also pairs the selected component (mode tm,
fixed_mode) with the annotation note component (rsc329 in section rsc832),
so each historical change can carry a note.
Relations & projects
- The Relations card builds a
relation_listinstance keyed bycontext.config.relation_list_tipo(set server-side incommonwhen the section declares a relation-list trigger). It renders the inbound "who references me" view — see component_relation_index and relation_list. - The Project card surfaces the section's component_filter
node, captured from the
render_component_filter_<section_tipo>event, letting the curator change the record's project assignment from the panel.
Section coupling
The section orchestrates the client inspector's whole lifecycle: it
constructs the inspector whenever it renders in mode==='edit' with
sufficient permissions, feeds it context/data/tools, and the section
view moves the paginator into inspector.paginator_container. There is no
separate server-side flag gating it — the mode + permission check is the
whole gate. See section for the section instance
the inspector treats as its source of truth, and dd_object
for the context/tools/show_in_inspector shape it consumes.
Examples
A tool opting into the inspector (server registration)
A tool whose context arrives with show_in_inspector: true (resolved by
tool_common) becomes an inspector button automatically. The context the client
filters on looks like:
{
"name" : "tool_propagate_component_data",
"model" : "tool_propagate_component_data",
"label" : "Propagates component data",
"section_tipo" : "dd1324",
"mode" : "edit",
"icon" : "/dedalo/tools/tool_propagate_component_data/img/icon.svg",
"show_in_inspector" : true,
"show_in_component" : true,
"type" : "tool"
}
The inspector then renders its button and, on mousedown, opens it:
// render_inspector.js → get_content_data()
const inspector_tools = self.caller.context.tools.filter( el => el.show_in_inspector )
// for each tool_context: build a .light.blank button and …
open_tool({
tool_context : tool_context,
caller : self.caller // the section instance
})
Reading the raw record behind the panel
The bottom "view record data" button and the read_raw RQO the inspector builds:
// inspector.prototype.get_raw_record_rqo()
const rqo = {
action : 'read_raw',
options : {
type : 'section',
section_tipo : self.caller.section_tipo,
tipo : self.caller.section_tipo,
model : self.caller.model
},
sqo : {
section_tipo : [ self.caller.section_tipo ],
limit : 1,
filter_by_locators : [{
section_tipo : self.caller.section_tipo,
section_id : self.caller.section_id
}]
},
pretty_print : true
}
Toggling the rail and persisting width
// collapse/expand to the icon rail; reflows the layout via --inspector_width
toggle_inspector_rail(self) // persists { id:'inspector_rail_state', value:true } in the 'status' table
Related
- section — the caller; owns the inspector's lifecycle
and supplies its
context/data/tools/ paginator. - Sections concept — the record model the panel reflects.
- Components — components, the
context.toolstoolbar, and theshow_in_inspector/show_in_componentflags. - dd_object (ddo) — the context object carrying
show_in_inspector,tools,config.relation_list_tipo. - component_filter — the node shown in the Project card.
- component_relation_index — the inbound relations surfaced by the Relations card.
- Events — the
activate_component/deactivate_component/save/render_<id>events the inspector subscribes to.