From f3a17a6b0ba4822690ffa5bc0458e65c3bd317a6 Mon Sep 17 00:00:00 2001 From: damienriehl Date: Sat, 2 May 2026 11:56:23 -0500 Subject: [PATCH 01/10] =?UTF-8?q?docs(plan):=20Phase=2017=20=E2=80=94=20Gr?= =?UTF-8?q?aph=20as=20entity-scoped=20tab=20+=20Source=20migration=20to=20?= =?UTF-8?q?right=20pane?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the design contract and four validation sketches for Phase 17 (v0.5.0): move the entity graph and the source view from their current homes (peer-tab in Developer + hidden icon in Standard for graph; left-pane Tree|Source mode strip + entity-actions Source link for source) into a unified right-pane tab strip — Detail | Graph in Standard, Detail | Graph | Source in Developer. Design contract: .planning/notes/graph-as-entity-pane.md · 11 decisions covering layout, click semantics, tab persistence, Source-as-Developer-tab, modal⇄Maximize round-trip with Restore terminology, and tree→source auto-jump behavior spec. Sketches: .planning/sketches/{001..004}/index.html · 001 tab-strip-language → winner D (icon + label, ABOVE entity header) · 002 standard-view-layout → winner D (Compact density + 50/50 tabs) · 003 developer-view-and-modal → applied + Source-as-third-tab restructure · 004 open-full-source → winner C (Modal ⇄ Maximize round-trip with "Restore" matching Windows OS convention) Roadmap: appended Phase 17 entry. Tracker: ontokit-web#229 (under v0.5.0 milestone #214). No code changes — design and planning artifacts only. Implementation lands on its own feature branch via /gsd-spec-phase → /gsd-discuss-phase → /gsd-plan-phase → /gsd-execute-phase, gated on PR #88 (entity graph) merging to dev first. Co-Authored-By: Claude Opus 4.7 --- .planning/ROADMAP.md | 19 +- .planning/notes/graph-as-entity-pane.md | 149 +++ .../sketches/001-tab-strip-language/README.md | 43 + .../001-tab-strip-language/index.html | 373 +++++++ .../002-standard-view-layout/README.md | 54 + .../002-standard-view-layout/index.html | 919 ++++++++++++++++++ .../003-developer-view-and-modal/README.md | 56 ++ .../003-developer-view-and-modal/index.html | 759 +++++++++++++++ .../sketches/004-open-full-source/README.md | 67 ++ .../sketches/004-open-full-source/index.html | 659 +++++++++++++ .planning/sketches/MANIFEST.md | 20 + .planning/sketches/themes/default.css | 212 ++++ 12 files changed, 3329 insertions(+), 1 deletion(-) create mode 100644 .planning/notes/graph-as-entity-pane.md create mode 100644 .planning/sketches/001-tab-strip-language/README.md create mode 100644 .planning/sketches/001-tab-strip-language/index.html create mode 100644 .planning/sketches/002-standard-view-layout/README.md create mode 100644 .planning/sketches/002-standard-view-layout/index.html create mode 100644 .planning/sketches/003-developer-view-and-modal/README.md create mode 100644 .planning/sketches/003-developer-view-and-modal/index.html create mode 100644 .planning/sketches/004-open-full-source/README.md create mode 100644 .planning/sketches/004-open-full-source/index.html create mode 100644 .planning/sketches/MANIFEST.md create mode 100644 .planning/sketches/themes/default.css diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index dbb8652f7..2841a32a8 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -202,9 +202,26 @@ Plans: - [x] 16-04-PLAN.md — Integration: Review page wiring + visual verification **UI hint**: yes +### Phase 17: Graph as Entity-Scoped Tab in Detail Pane +**Milestone**: v0.5.0 +**Goal**: Move the entity graph from a peer-tab in Developer view (and a hidden icon button in Standard view) into a `Detail | Graph` tab strip at the top of the right-side detail pane, applied uniformly across both views and across all three entity types (class, property, individual). Property graphs render real domain/range/parent-property edges (an explicit improvement over WebProtege's empty-island treatment). +**Depends on**: none (touches Standard and Developer layouts independently of other Phase 17+ work) +**Requirements**: derived from `.planning/notes/graph-as-entity-pane.md` decisions 1-8; to be formalized via /gsd-spec-phase +**Success Criteria** (what must be TRUE): + 1. Both Standard and Developer views render a `Detail | Graph` tab strip at the top of the right-side detail pane for all three entity types (class, property, individual). The Developer view's `Tree | Source | Graph` peer-tab strip is reduced to `Tree | Source` — no separate graph view. + 2. Single-click on a non-focus graph node re-centers the graph; double-click fully selects the entity (URL update, tree highlight, detail pane refresh). Pre-existing behavior preserved. + 3. The active tab persists across selection changes (clicking a different entity in the tree, in search, or via double-click in the graph keeps the user on whichever tab they were on). + 4. The default tab on first entity load in a new session is Detail; subsequent selections honor the user's last-active tab. + 5. The in-pane graph has an "Expand" affordance that opens the existing `EntityGraphModal` (no new modal component). + 6. Property graphs render domain classes, range classes, parent/child properties, and see-also as actual nodes/edges — not a single isolated focus node. Annotation properties render an empty-state message when they have no domain/range/see-also. + 7. The Standard view's prior small "Graph" icon button in the detail panel is removed; the new tab strip is its only graph entry point in that view. +**Plans**: TBD (4-6 plans expected: data-layer changes for property/individual graph endpoints, tab-strip component, Standard view integration, Developer view integration, modal-expand wiring, visual verification) +**UI hint**: yes +**Refs**: `.planning/notes/graph-as-entity-pane.md` (decision capture) + ## Progress -**Execution Order:** 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 +**Execution Order:** 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15 -> 16 -> 17 | Phase | Milestone | Plans Complete | Status | Completed | |-------|-----------|----------------|--------|-----------| diff --git a/.planning/notes/graph-as-entity-pane.md b/.planning/notes/graph-as-entity-pane.md new file mode 100644 index 000000000..6bdf1fc7e --- /dev/null +++ b/.planning/notes/graph-as-entity-pane.md @@ -0,0 +1,149 @@ +--- +title: Graph as Entity-Scoped Tab in Detail Pane — Design Decisions +date: 2026-05-02 +context: Pre-phase exploration + sketch session; captured for Phase 17 spec/plan. +references: + - WebProtege Entity Graph (stacked under detail panel) + - Folio Enrich (Candidate Details | Entity Graph tab strip) + - Current OntoKit Standard view (Graph icon button in right-side detail panel) + - Current OntoKit Developer view (Graph as peer-tab next to Tree/Source) +sketches: + - .planning/sketches/001-tab-strip-language/ (winner: D — icon + label, ABOVE entity header) + - .planning/sketches/002-standard-view-layout/ (winner: D — Compact + 50/50 tabs) + - .planning/sketches/003-developer-view-and-modal/ (winner: applied + Source-as-third-tab restructure) + - .planning/sketches/004-open-full-source/ (winner: C — Modal ⇄ Maximize round-trip) +--- + +# Graph as Entity-Scoped Tab in Detail Pane + +The graph today is misplaced in both views: a peer-tab in Developer (alongside Tree/Source — implies it's a *navigation surface*, not an entity-scoped view), and a small icon button in Standard that users miss entirely. This note captures the design decisions made during exploration (Decisions 1-8) and refined through a four-sketch validation pass (Decisions 9-11 + visual specs). + +## Problem statement + +A graph is fundamentally an entity-scoped view — it shows a focused class/property/individual surrounded by its neighborhood. The current placement in both views obscures that relationship. Users in Standard view don't realize the graph exists. Users in Developer view treat it as a separate mode they switch to, breaking the connection between the selected entity and what's rendered. + +## The design contract (11 decisions) + +### 1. Purpose: read-only neighborhood view (verify + explore) + +The graph is a read-only context view. It helps users see the selected entity in relation to its siblings, parents, cousins. It is not a manipulation surface (no drag-to-reparent in this scope) and not a presentation/screenshot surface (that's a side benefit). + +### 2. Layout: tabbed swap inside the existing detail pane + +The right-side detail pane gains a tab strip at the top. The two (Standard) or three (Developer) tabs swap content in the same pane. Same screen real estate as today; tabs are one click away; swapped content is never visible simultaneously. + +**Sketch refinements (from 001 + 002):** +- **Tab strip sits ABOVE the entity header** (not below). Reads as "Entity workspace with mode selector," not "Entity card with buried tab." Sketch 001 Variant D. +- **Compact density** — tab buttons use 8px vertical padding + text-xs, matching the left-pane tree-tabs strip exactly so the bottom borders align horizontally across the whole editor. Sketch 002 Variant D. +- **Tabs span the full pane width** — `flex: 1 1 0` so 2 tabs are 50/50, 3 tabs are 33/33/33. Reads as a major mode-selector, not a small control collapsed to one side. +- **Active state** — accent underline + accent-tinted background fill. Same visual language as the left-pane tree-tabs (Classes / Properties / Individuals), so the two strips read as a coherent family. +- **Icons + sentence-case labels** — Lucide `file-text` for Detail, a 3-node graph glyph for Graph, `chevrons-left-right` for Source. Underlines indicate active. + +### 3. Old peer-tab Graph: removed entirely + +Developer view's `Tree | Source | Graph` peer-tab strip is removed. Graph is *only* ever in the right-pane tab strip. Standard view's small icon button is also removed. + +**Sketch refinement (from 003):** the entire left-pane mode strip (Tree | Source) is removed. The left pane shows only the tree (with `Classes | Properties | Individuals` sub-tabs). Source migrates to the right pane as a third tab — see Decision 9. + +### 4. Full-screen affordance: reuse existing EntityGraphModal + +The in-pane graph has an "Expand" button (top-right of the canvas). Clicking it opens the existing `EntityGraphModal` component as a centered overlay above the editor, dismissable via Escape / click-outside / close button. Same component reused for both Standard and Developer views. + +### 5. Entity-type scope: all three (class, property, individual) + +Class, Property, and Individual detail panes each get the tab strip. The graph component adapts per entity type: + +- **Class:** parent classes, child classes, siblings, equivalent classes, disjoint classes, see-also. +- **Property:** **domain classes, range classes**, parent/child properties, see-also. _Explicit improvement over WebProtege, which leaves the property graph as an isolated single-node island._ OntoKit's property graph must show the relationships that make a property meaningful. +- **Individual:** class assertions (rdf:type), object property values, see-also, sameAs. + +**Edge case (defer to plan/build):** Annotation properties typically have no domain/range. Graph for them shows just see-also if any, plus a friendly empty state otherwise. + +### 6. Click behavior: preserve current single/double-click semantics + +- **Single-click on a non-focus node** → re-center the graph on that node (graph navigation only; global selection unchanged). +- **Double-click on a non-focus node** → fully select that entity (URL updates, Tree highlights, Detail tab shows new entity). + +Preserves the "explore the graph without losing your place" model that's a real differentiator vs WebProtege. + +### 7. Tab persistence: stay on current tab across selection changes + +When the user picks a new entity (tree click, search, or double-click in graph), the active tab does *not* reset. If they were on Graph, they stay on Graph. If on Detail, they stay on Detail. If on Source (Developer-only), they stay on Source. + +### 8. Default tab on first load: Detail + +First entity opened in a new session lands on the Detail tab. Decision 7 then keeps the user on whichever tab they choose for the rest of the session. + +### 9. Source as right-pane Developer-only tab _(new — from sketch 003)_ + +The Source affordance migrates from two places that no longer exist: + +- The left-pane `Tree | Source` peer-tab strip (removed per Decision 3 refinement). +- The ` Source` link button in the entity-actions area of the detail header (removed). + +…into a **third tab in the right-pane tab strip** alongside Detail and Graph. Source tab is **conditionally rendered** based on editor mode: + +- **Standard view:** right pane has `Detail | Graph` (2 tabs at 50/50). No Source tab. +- **Developer view:** right pane has `Detail | Graph | Source` (3 tabs at 33/33/33). + +The Source tab shows the **entity-scoped Turtle snippet** — just the block defining the currently-selected entity, with a comment line indicating where in the full file it lives (e.g., `# File: ontology.ttl, line 4,217`). A toolbar above the snippet exposes: + +- **↗ Open full source** — opens the full ontology source (see Decision 10). +- **⎘ Copy snippet** — copies the entity-scoped snippet to clipboard. + +### 10. "Open Full Source" — Modal ⇄ Maximize round-trip _(new — from sketch 004)_ + +When the user clicks **↗ Open full source** from the Source tab, a centered **modal** opens at ~92% viewport with a dim backdrop, reusing the same modal frame component as `EntityGraphModal`. Inside the modal, the user can: + +- **Stay in modal** — Esc / click-outside / ✕ dismisses fully back to the Source tab. +- **⤢ Maximize** (modal header) — promotes the modal to a full editor takeover (tree + detail hidden; only the app header and project bar remain). Inside the takeover: + - **⊟ Restore** (takeover header) — returns to the modal view. Bouncing maximize ↔ restore is symmetric and preserves scroll position. + - **← Back to entity** — dismisses fully back to the Source tab. + +**Terminology:** the takeover-side button is labelled **"Restore"** (matching Windows OS Maximize ↔ Restore convention) with tooltip *"Restore to modal view."* "Minimize" was rejected because in OS land it means send-to-taskbar (hide entirely), not return-to-previous-size — the wrong mental model. + +**Round-trip integrity:** the Restore button is only shown when the takeover was reached via the modal's Maximize. If a user reaches the takeover by some other path (none currently designed, but kept open as a constraint), the Restore button is absent — preserving the chain. + +**State:** +- A `cameFromModal` boolean tracks whether the takeover was reached via Maximize. +- Esc dismisses fully from either size; Restore only changes size. +- Active right-pane tab (Detail / Graph / Source) doesn't change when the source overlay opens or closes. + +### 11. Tree → Source auto-jump (behavior spec) _(new — from sketch 004 follow-up)_ + +When the user has the Source tab active and selects a different entity in the Tree (or via search), the Source tab's content auto-updates to show the newly-selected entity's snippet — no extra click. + +If the **full-source overlay is also open** (in either modal or maximized size), it auto-scrolls to the new entity's lines and re-applies the highlight rule. The overlay does not dismiss on selection change. + +This complements Decision 7 (tab persistence) and Decision 10 (round-trip) — together they make the Source tab feel like a continuously-synced view of "whatever you're looking at in the tree." + +## Implicit consequences (worth flagging for the plan) + +- **Standard view's small Graph icon button** in the right-side detail panel — the affordance the user described as "not obvious" — is removed. The new tab strip replaces it. +- **Developer view's Tree | Source peer-tab strip** is removed entirely. The left pane is just the tree. +- **` Source` link in entity-actions** is removed in both views (replaced by the Source tab in Developer; not exposed at all in Standard). +- **Tab strip component** is shared across both views but receives a `tabs` prop that drives `flex: 1 1 0` layout: 2 tabs in Standard, 3 in Developer. One component, two configurations. + +## Visual decisions captured from sketches + +| What | Decision | Source | +|---|---|---| +| Tab strip position | Above the entity header (topmost chrome of the right pane) | Sketch 001 D | +| Tab styling | Icon + sentence-case label, accent-fill on active, accent underline | Sketch 001 D | +| Tab density | Compact: 8px vertical padding, text-xs, matching left tree-tabs height | Sketch 002 D | +| Tab width | `flex: 1 1 0` — fills the pane (50/50 in Standard, 33/33/33 in Developer) | Sketch 002 D | +| Standard view layout | 2-pane: Tree (~280px) \| Detail/Graph (rest) | Sketch 002 | +| Developer view layout | 2-pane: Tree (~280px, no mode strip) \| Detail/Graph/Source (rest) | Sketch 003 | +| Modal frame | Reuse `EntityGraphModal` shell — same border radius, shadow, dismissal | Sketch 004 | +| Open Full Source default | Modal at ~92% viewport, dim backdrop | Sketch 004 C | +| Open Full Source escape | ⤢ Maximize → full editor takeover; ⊟ Restore returns to modal | Sketch 004 C | +| Restore button label | "Restore" (Windows convention; rejected: Minimize, Exit fullscreen, Back to Modal) | Sketch 004 | + +## Out of scope for Phase 17 + +- Drag-to-reparent in the graph. +- Per-user / per-project default tab preference. +- Graph for properties of properties (rdf:Property meta-level). +- Graph export to SVG/PNG for screenshots (separate feature). +- Source view editing UX changes (the new tab inherits whatever the current Source view does for keybindings, autocomplete, etc.). +- Two-way navigation from full-source line clicks back to tree selection (existing source-IRI index already supports it; just preserve behavior). diff --git a/.planning/sketches/001-tab-strip-language/README.md b/.planning/sketches/001-tab-strip-language/README.md new file mode 100644 index 000000000..e080c5fa6 --- /dev/null +++ b/.planning/sketches/001-tab-strip-language/README.md @@ -0,0 +1,43 @@ +--- +sketch: 001 +name: tab-strip-language +question: "What should the Detail | Graph tab strip look like — label, casing, icon, position?" +winner: "D" +tags: [tab-strip, typography, layout] +--- + +# Sketch 001: Tab Strip Language + +## Design Question + +The Phase 17 design is settled on a tab strip in the right-side detail pane that swaps `Detail` and `Graph`. Open: label wording, casing, whether icons are needed, and **whether the strip sits above or below the entity header**. The answer here propagates to both Standard and Developer views and to all three entity types. + +## How to View + +`open .planning/sketches/001-tab-strip-language/index.html` + +## Variants + +- **A: Folio caps** — `DETAIL | ENTITY GRAPH` in caps with letter-spacing, mirrors Folio Enrich. Heaviest visual weight; loud and obvious; fixes discoverability instantly. +- **B: Sentence soft** — pill-style segmented control (`Detail` / `Graph`). macOS-y. Quietest; defers to entity content; risks under-discoverability. +- **C: Icon + label, BELOW entity header** — Lucide icons + sentence-case (`◉ Detail` / `◇ Graph`), underline + accent tint on active. Tab strip sits beneath the entity name (the layout in the original Folio Enrich screenshot). +- **D: Icon + label, ABOVE entity header** ★ **WINNER** — Same icon-and-label treatment as C, but the tab strip is the topmost chrome of the pane. The entity header (pip + name + IRI) becomes content of whichever tab is active. + +## Winning Insight + +The tab strip works best as the topmost chrome of the pane because: + +1. **Maximum discoverability** — the tab strip is the first thing the eye lands on when scanning the right side of the screen. Fixes the original "not obvious" complaint without resorting to caps/loud styling. +2. **Persistent orientation** — the entity header (pip + name + IRI) sits beneath the tab strip and stays visible when switching modes, so the user never loses context. Reads as: *"I am viewing <Triune God>, in <Detail> mode."* +3. **Unified pane semantics** — the whole right pane reads as an "Entity workspace" with a mode selector at the top, rather than an "Entity card with a buried tab control." + +## Watch-Outs for Subsequent Sketches + +- Tab strip + entity header costs ~88px of vertical space before content begins (vs ~62px in variant C). Sketch **002** must validate this density still works in Standard view's compact right pane without pushing the form fields below the fold. +- Tab strip styling: underline + accent fill on active. Carry these tokens into the real implementation via the existing `--color-primary-*` palette. +- Lucide icons used: `file-text` (Detail) and a custom 3-node graph glyph (Graph). The graph glyph should be replaceable with whatever icon the implementation team picks (lucide `Network`, `Workflow`, or `Share2` are all candidates). + +## What to Look For When Reviewing + +- Toggle dark mode via the corner toolbar — does the active state still read clearly? +- Click the inactive tab — confirm the swap feels right (no flicker, predictable highlight). diff --git a/.planning/sketches/001-tab-strip-language/index.html b/.planning/sketches/001-tab-strip-language/index.html new file mode 100644 index 000000000..e9ff29349 --- /dev/null +++ b/.planning/sketches/001-tab-strip-language/index.html @@ -0,0 +1,373 @@ + + + + + Sketch 001 — Tab Strip Language + + + + + +
+ Sketch 001 — tab strip language + + + + +
+ + + + + + + + + + + +
+
+
Variant D · Detail tab active · ★ Selected
+

Icon + sentence-case label, tab strip ABOVE the entity header

+

Same icon-and-label treatment as C, but the tab strip is the topmost chrome of the pane. The entity header (pip + name + IRI) becomes content of whichever tab is active — when you switch from Detail to Graph, the body changes but the entity header stays visible as persistent context. Reads as: "I am viewing <Triune God>, in <Detail> mode."

+ +
+
+ + +
+
+
C
+
+
Triune God
+
https://ontology.catholicos.catholic/R7xwsOkoji8EZRUrS511KGF
+
+
+
+
Primary Label
Triune God
+
Definition
The Triune God refers to the one true God who exists eternally as three distinct divine Persons.
+
Parent(s)
Deity
+
+
+ +
+
Pros
Tab strip is the first thing the eye lands on — maximum discoverability, fixes the original "not obvious" complaint. Entity header still anchors the pane so the user doesn't lose orientation when switching modes. Reads as a unified "Entity workspace" instead of "Entity card with a buried tab."
+
Cons
Tab strip + entity header costs ~88px of vertical space at the top before content begins (vs ~62px in C). May feel top-heavy in Standard view's compact pane — to be validated in sketch 002.
+
+
+
+ + +
+ Theme + +
+ + + + diff --git a/.planning/sketches/002-standard-view-layout/README.md b/.planning/sketches/002-standard-view-layout/README.md new file mode 100644 index 000000000..6d09cfb2c --- /dev/null +++ b/.planning/sketches/002-standard-view-layout/README.md @@ -0,0 +1,54 @@ +--- +sketch: 002 +name: standard-view-layout +question: "Does the chosen tab strip (Variant D from sketch 001) fit Standard view's 2-pane layout, and at what density?" +winner: "D" +tags: [standard-view, density, layout, tab-strip] +--- + +# Sketch 002: Standard View Layout + +## Design Question + +Sketch 001 settled the tab-strip language (icon + sentence-case label, sitting ABOVE the entity header). This sketch validates that the chosen treatment fits Standard view's actual layout — **two panes only**: Tree (~280px on the left) + Detail/Graph (the rest of the viewport on the right). Three densities were explored, then the winner was refined to fix a tab-collapse issue. + +## Layout correction (from initial draft) + +The first version of this sketch incorrectly imagined a third "center pane" between Tree and Detail. **There is no center pane in Standard view.** With Detail/Graph taking the entire right side of the screen, the Graph tab gets a much wider canvas than initially shown — large enough to render a full neighborhood (parent, focus, siblings, multiple children) without cramping. + +This means the density tradeoff is now mostly vertical (chrome height vs form-field/graph-canvas height). + +## How to View + +`open .planning/sketches/002-standard-view-layout/index.html` + +## Variants + +- **A: Compact** — text-xs tab labels, 24px entity pip, 8px tab padding. Tabs collapsed to the left, content-sized. +- **B: Comfortable** — text-sm tab labels, 28px entity pip, 12px tab padding. Tabs collapsed to the left. +- **C: Spacious** — text-base tab labels, 32px entity pip, 16px tab padding. Tabs collapsed to the left. +- **D: Compact + 50/50 tabs** ★ **WINNER** — Compact density (A's chrome size) with each tab spanning **50% of the entire right pane**. Detail and Graph are visually equal-weight affordances; the click target stretches the full pane width. + +## Winning Insight + +Compact density wins on space efficiency (the tab strip + entity header chrome consumes minimum vertical space, leaving the most room for form fields or graph canvas). But the original A had a discoverability issue: **the tabs were collapsed to the left of the pane**, which: +- Visually understates the importance of the Graph affordance +- Makes the right side of the strip feel hollow +- Reduces the click target to ~80px wide instead of ~half-pane + +Variant D fixes that by making each tab `flex: 1 1 50%` — the strip now reads as a true mode selector spanning the full pane width. The two tabs are perceived as peers, not as a small control hugging one side. + +## Watch-Outs for Implementation + +- **Class on container:** apply via `data-tabstrip="full"` attribute (or a CSS class) on the tabstrip wrapper. CSS rule: `flex: 1 1 50%; justify-content: center;` on `.pane-tabstrip button`. +- **Centered text + icon:** label and icon should be visually centered within each 50% half (use `justify-content: center` on the button). +- **Padding still applies:** keep ~11px vertical padding so the click target is comfortable; the horizontal padding becomes irrelevant since flex-grow eats whatever's left. +- **Future-proofing:** if a third tab is ever added (e.g., History, Diff), the same `flex: 1` rule auto-divides into thirds. Verify the tab labels still fit at narrower widths. +- **Tree-pane tabs (Classes / Properties / Individuals)** keep their original collapsed-left treatment — they're a different kind of control (entity-type filter, not view-mode swap), so they shouldn't compete with the new pane tab strip's full-width emphasis. + +## What to Look For When Reviewing D + +- Does the tab strip read as a major mode-selector now (vs the buried-button feel in A)? +- Does centered icon+label still feel balanced, or does it look stretched? +- Toggle dark mode via corner toolbar — does the active state's accent fill still read clearly across the wider button? +- Click Detail / Graph — confirm the swap feels right; the entity header (pip + name + IRI + actions) stays put. diff --git a/.planning/sketches/002-standard-view-layout/index.html b/.planning/sketches/002-standard-view-layout/index.html new file mode 100644 index 000000000..2e2b58fd1 --- /dev/null +++ b/.planning/sketches/002-standard-view-layout/index.html @@ -0,0 +1,919 @@ + + + + + Sketch 002 — Standard View Layout + + + + + +
+ Sketch 002 — standard view layout (2-pane) + + + + +
+ + + + + + + + + + + +
+
Variant D · Compact density · tabs span 50% each of the right pane · Detail tab active. Click Graph to swap. ★ Selected
+
+
+
OntoKit
+ +
+ main · d484ded + Health +
+
+ Catholic Semantic Canon + 14,973 classes +
+
+
+
+
+ +
+
+ + +
+
+
C
+
+
Triune God
+
https://ontology.catholicos.catholic/R7xwsOkoji8EZRUrS511KGF
+
+
+ + +
+
+
+
Label
Triune God
en
+
Definition
The Triune God refers to the one true God who exists eternally as three distinct divine Persons: Father, Son, and Holy Spirit. Each Person fully possesses the one divine essence, equal in divinity and united in communion without division or hierarchy.
lang
+
Comment(s)
Add another Comment — or translation.
+
Example(s)
Catholic theology treats the Triune God as the central mystery of faith.
+
Parent(s)
CDeity
+
Relationships
+ Add relationship
+
Statistics
3 subclasses · — instances
+
+ +
+
+
+
+ + +
+ Theme + +
+ + + + diff --git a/.planning/sketches/003-developer-view-and-modal/README.md b/.planning/sketches/003-developer-view-and-modal/README.md new file mode 100644 index 000000000..eb525ed21 --- /dev/null +++ b/.planning/sketches/003-developer-view-and-modal/README.md @@ -0,0 +1,56 @@ +--- +sketch: 003 +name: developer-view-and-modal +question: "Does the right-pane tab strip work as Detail | Graph | Source in Developer view (Source absent in Standard)?" +winner: null +tags: [developer-view, modal, layout, tab-strip] +--- + +# Sketch 003: Developer View + Modal Expand + +## Design Question + +Sketch 002 established the right-pane tab strip with **two** tabs (`Detail | Graph`) for Standard view. Developer view extends that strip to **three** tabs (`Detail | Graph | Source`) — the Source tab subsumes both: + +1. The current `Tree | Source` peer-tab strip in the left pane (which is now removed entirely — left pane shows only the tree). +2. The current ` Source` link button in the entity-actions of the detail panel (also removed). + +Source is **Developer-only**: Standard view never shows the Source tab. The tab is conditionally rendered based on the active editor mode. + +## Layout corrections from prior drafts + +- **No left-pane mode strip.** The left pane now shows only the tree (with `Classes | Properties | Individuals` sub-tabs). The previous `Tree | Source` 50/50 strip is gone. +- **No ` Source` button in entity-actions.** Replaced by the Source tab in the right pane. +- **Source as entity-scoped.** The Source tab shows the Turtle snippet for the *currently selected* entity (with line number reference and an "Open full source" affordance to dive into the complete file). Consistent with Detail and Graph also being entity-scoped. + +## How to View + +`open .planning/sketches/003-developer-view-and-modal/index.html` + +## States + +- **State A — Detail tab active.** Initial load. Verify: left pane is just the tree (no Tree/Source toggle); right pane has 3 tabs; entity-actions has Copy IRI but no Source link. +- **State B — Graph tab active.** Inline neighborhood graph, ⤢ Expand button top-right. +- **State C — Source tab active.** Entity-scoped Turtle snippet with toolbar (Open full source, Copy snippet). Visible only because we're in Developer view. +- **State D — Modal expanded.** EntityGraphModal opens over the editor with extra room. Esc/click-outside/✕ to dismiss. + +Cycle states via the variant tabs at the top, or interact directly: +- Click Detail / Graph / Source in the right-pane tab strip +- Click ⤢ Expand on the Graph tab to open the modal + +## What to Look For + +- **Left pane consolidation feels right.** Removing Tree/Source frees the left pane to be exactly one thing: the entity hierarchy. Confirm the absence doesn't feel "where did Source go?" — the user should naturally find it as the third tab in the right pane. +- **Three tabs at 33/33/33.** Same icon + label treatment as the 2-tab Standard view, but each tab now spans 33% of the pane width. Verify the three tabs read as peers (no one tab dominating). +- **Source as entity-scoped.** Developer-mode users coming from the old "full file source" mental model should see "Triune God — entity-scoped definition" comment + line number ("File: ontology.ttl, line 4,217") + the "Open full source" button. Together those signal "this is the snippet, full file is one click away." +- **Modal still works.** The Expand button on the Graph tab still opens the modal. +- **Dark mode.** Toggle via corner toolbar — the Source view's syntax-highlighted Turtle should still read clearly. + +## Watch-Outs for Implementation + +- **Conditional tab rendering.** `Source` tab only renders when `editorMode === "developer"`. In Standard view, the right pane's tab strip becomes 2-tab (Detail | Graph) at 50/50; in Developer it's 3-tab at 33/33/33. The tab-strip component should accept a `tabs` prop and lay them out with `flex: 1 1 0` so the same component handles both cases. +- **Source tab persistence.** If a user is on the Source tab in Developer view and switches to Standard mode, the active tab should fall back to whatever they were last on (Detail or Graph). The selection-store's `mode`/active-tab tracking needs a fallback path. +- **Old Tree/Source state.** Removing the left-pane Tree/Source toggle means the existing `viewMode` state in `DeveloperEditorLayout` is dropped — the left pane is always the tree now. The Source content moves into the right pane's tab body. +- **Entity-scoped source rendering.** New: needs a function `getEntitySourceSnippet(iri, ttlSource)` that finds the Turtle block for an IRI. Already exists in `lib/ontology/turtleClassUpdater.ts` (`findBlock`) — reuse it for the Source tab. +- **"Open full source" affordance.** Clicking it should open the existing full-file Source view in a modal or a new pane, scrolled to the entity's line. This preserves the "I need the whole file" workflow without making it the default. +- **Standard view's old Source link.** The ` Source` button in the entity-actions area is removed in both views (since Standard never had real source editing and Developer now has the Source tab). diff --git a/.planning/sketches/003-developer-view-and-modal/index.html b/.planning/sketches/003-developer-view-and-modal/index.html new file mode 100644 index 000000000..a1f7298ca --- /dev/null +++ b/.planning/sketches/003-developer-view-and-modal/index.html @@ -0,0 +1,759 @@ + + + + + Sketch 003 — Developer View + Modal Expand + + + + + +
+ Sketch 003 — developer view + modal expand + + + + +
+ +
+
State A · Detail tab active in the right pane · Left pane has only the tree (no Tree/Source mode strip). Click Graph or Source in the right pane to swap.
+
+
+
OntoKit
+ +
+ main · d484ded + Health +
+
+ Catholic Semantic Canon + 14,973 classes +
+
+
+
+
+ + + + + +
+
+
+ + + + + +
+ Theme + +
+ + + + diff --git a/.planning/sketches/004-open-full-source/README.md b/.planning/sketches/004-open-full-source/README.md new file mode 100644 index 000000000..96d95000a --- /dev/null +++ b/.planning/sketches/004-open-full-source/README.md @@ -0,0 +1,67 @@ +--- +sketch: 004 +name: open-full-source +question: "When a user clicks 'Open Full Source' from the entity-scoped Source tab, what's the right surface — and how does the user move between sizes?" +winner: "C" +tags: [source-view, modal, full-screen, layout, round-trip] +--- + +# Sketch 004: Open Full Source + +## Design Question + +The entity-scoped Source tab (added in Sketch 003) shows a Turtle snippet for the currently selected entity. From there, the user can click **↗ Open full source** to view the entire `ontology.ttl` file. What surface should "full source" use, and how should the user move between sizes? + +## How to View + +`open .planning/sketches/004-open-full-source/index.html` + +## Variants + +- **A: Modal only** — Centered overlay at ~92% viewport with dim backdrop; Esc / click-outside / ✕ dismiss. Same dismissal as `EntityGraphModal`. Best for "peek and close." Lacks an escape hatch when the user wants more room. +- **B: Full editor takeover (current behavior)** — Source view fills the entire editor canvas; tree + detail are hidden. ← Back to entity dismisses. Maximum source real estate; matches what OntoKit does today. No way to peek without committing. +- **C: Modal ⇄ Maximize (round-trip)** ★ **WINNER** — Click **↗ Open full source** → opens the modal (~92% viewport). Inside the modal, click **⤢ Maximize** → promotes to full takeover. Inside the takeover, click **⊟ Restore** → returns to modal. ✕ / Back to entity dismisses fully from either size. Bounce maximize ↔ restore freely. + +## Winning Insight + +Variant C is C+ — the original C added a Maximize escape hatch but had no way back. The round-trip refinement adds **⊟ Restore** as a paired affordance to ⤢ Maximize: + +- Both buttons share styling (accent-tinted, same `.maximize-btn` / `.minimize-btn` CSS rules) so they read as the same control inverted. +- Restore is **only shown** when the user reached the takeover via Maximize from the modal. Users who clicked "Open Full Source" with the variant-B-style direct takeover wouldn't see it (no modal to return to). +- The bounce is symmetric: peek (modal) → commit (Maximize) → peek again (Restore) → commit again (Maximize). The user never has to dismiss-and-reopen to switch sizes. +- Esc and click-outside still dismiss fully from the modal. The takeover dismisses via "← Back to entity" or via Restore then Esc. + +This pattern matches what OS-level windows do without inheriting their visual baggage. Discovery is helped by the Maximize button's tooltip ("Expand to full editor takeover") and the Restore button's tooltip ("Restore to modal view"). + +### Why "Restore" (not "Minimize") + +The button's user-facing label is **"Restore"** — the standard Windows OS title-bar pair to "Maximize." Rejected alternatives: + +- **"Minimize"** — wrong: in OS land "Minimize" means send-to-taskbar (hide entirely), not return-to-previous-size. Conflicts with users' OS mental model. +- **"Exit fullscreen"** — works only if we'd named the trigger "Fullscreen"; mismatched with our "Maximize" trigger. +- **"Restore Down"** — the more verbose Windows term; "Down" is implementation-specific and unnecessary. +- **"Back to Modal"** — descriptive but technical (users don't think in "modal"). +- **"Collapse"** — wrong nuance; implies hiding a panel, not resizing. + +The Lucide icon used is named `Minimize2` internally (an icon-library convention) but the user-facing label is "Restore." The icon shows two arrows pointing inward at corners — it contrasts directly with the Maximize icon (two arrows pointing outward). + +## What to Look For + +- **Try the round-trip in C:** Open Full Source → Maximize → Restore → Maximize → Esc. The user should never feel trapped; both directions are one click. +- **Discoverability:** Is the Maximize button readable enough as "make this bigger" vs being mistaken for "open in new tab"? Hover the button to see the tooltip. +- **Consistency with EntityGraphModal:** the same modal frame is reused. Visual continuity across "Expand graph" and "Open full source" — same border radius, shadow, dismissal affordances. +- **The Restore icon:** the round-trip glyph is Lucide's `Minimize2` — two arrows pointing inward at corners. Contrasts directly with the Maximize glyph. Toggle dark mode to verify both still read clearly. +- **Highlight scrolling:** in either size, the entity's source block is highlighted with a left accent rule + blue tint and auto-scrolled into view. + +## Watch-Outs for Implementation + +- **Reuse the modal frame.** Same component as `EntityGraphModal` — only the body content differs. New props: `body` slot, `headerActions` slot. +- **Track origin in state.** A `cameFromModal` boolean on the source-overlay state. Set true when the takeover is reached via Maximize. Used to show/hide the Restore button. Reset on close. +- **Two affordances, paired styling.** The Maximize button (in the modal header) and the Restore button (in the takeover header) should share visual styling — accent-tinted background, same border, same icon family. They're conceptually one toggle, just split across two surfaces. +- **Esc dismisses, Restore doesn't.** Pressing Esc in either size dismisses fully (closes the source overlay entirely). Clicking Restore only changes size. Avoids the trap where Esc takes you back to modal but you wanted to dismiss. +- **Active-tab persistence.** When the user closes the source overlay (any size), they return to the right-pane Source tab. Their tab selection is unchanged. +- **Scroll position persistence within session.** When bouncing between modal and takeover, the source view's scroll position should persist (don't re-scroll to the entity's lines on every size change — only on initial open). + +## Related to Idea 2 (Tree → Source jump) + +When a user selects a different entity in the Tree while the **Source tab is active**, the entity-scoped source should auto-update to show the newly-selected entity's snippet. **If the full-source overlay is also open** (in either size), it should auto-scroll to the new entity's lines. This is a behavior spec that complements C's round-trip pattern — both modal and takeover need the same scroll-on-selection behavior. diff --git a/.planning/sketches/004-open-full-source/index.html b/.planning/sketches/004-open-full-source/index.html new file mode 100644 index 000000000..26c895e7f --- /dev/null +++ b/.planning/sketches/004-open-full-source/index.html @@ -0,0 +1,659 @@ + + + + + Sketch 004 — Open Full Source + + + + + +
+ Sketch 004 — Open Full Source + + + +
+ +
+
Variant C ★ · Round-trip · ↗ Open full source opens the modal. Inside the modal, ⤢ Maximize promotes it to full takeover. Inside the takeover, ⊟ Restore sends it back to modal (Windows-style Maximize ↔ Restore pair). Bounce freely between the two.
+ +
+
+
OntoKit
+ +
+ main · d484ded + Health +
+
+ Catholic Semantic Canon + 14,973 classes +
+
+
+
+
+ + +
+
+ + + + + +
+
+ +
+ + ontology.ttl + — full source · 14,973 classes · 87,420 lines · scrolled to Triune God (line 4,217) +
+ +
+
+ No editor chrome behind this view — tree and detail are hidden until you go back. + +
+
+
+
+ + +
+ Theme + +
+ + + + diff --git a/.planning/sketches/MANIFEST.md b/.planning/sketches/MANIFEST.md new file mode 100644 index 000000000..6667d4036 --- /dev/null +++ b/.planning/sketches/MANIFEST.md @@ -0,0 +1,20 @@ +# Sketch Manifest + +## Design Direction + +Phase 17 — move the entity graph from a peer-tab in Developer view (and a small icon button in Standard view) into a `Detail | Graph` tab strip at the top of the right-side detail pane. Visual language matches OntoKit's existing app: sky-blue primary (`--color-primary-*`), slate neutrals, OWL-entity accent colors (class=green, property=blue, individual=pink), light + dark via CSS data-attribute. Folio-Enrich tab strip is the closest reference; WebProtege's docked entity graph is the loose mental model. Read `.planning/notes/graph-as-entity-pane.md` for the eight design decisions that constrain these sketches. + +## Reference Points + +- **Folio Enrich** — `CANDIDATE DETAILS | ENTITY GRAPH` tab strip in caps, graph-bracket icon, dark theme. +- **WebProtege** — entity graph as a docked pane below the entity detail; property graph is an isolated single-node island (a problem we want to fix). +- **OntoKit current state** — Developer has Graph as a peer-tab next to Tree/Source; Standard has a small icon button hidden in the right-side detail panel. + +## Sketches + +| # | Name | Design Question | Winner | Tags | +|---|------|----------------|--------|------| +| 001 | tab-strip-language | What should the `Detail \| Graph` tab strip look like — label, casing, icon, position? | **D — icon + label, ABOVE entity header** | tab-strip, typography, layout | +| 002 | standard-view-layout | Does the chosen tab strip fit Standard view's 2-pane layout, and at what density? | **D — Compact + 50/50 tabs** | standard-view, density, layout, tab-strip | +| 003 | developer-view-and-modal | Does the right-pane tab strip work as Detail \| Graph \| Source in Developer view (Source absent in Standard)? | **Applied — 3 tabs at 33/33/33; left pane is just the tree; Source absent in Standard** | developer-view, modal, layout, tab-strip | +| 004 | open-full-source | When the user clicks "Open Full Source" — what surface, and how does the user move between sizes? | **C — Modal ⇄ Maximize round-trip** | source-view, modal, full-screen, round-trip | diff --git a/.planning/sketches/themes/default.css b/.planning/sketches/themes/default.css new file mode 100644 index 000000000..5e7d82283 --- /dev/null +++ b/.planning/sketches/themes/default.css @@ -0,0 +1,212 @@ +/* OntoKit sketch theme — mirrors app/globals.css tokens */ +:root { + color-scheme: light dark; + + /* Sky-blue primary (matches OntoKit's --color-primary-* scale) */ + --color-primary-50: #f0f9ff; + --color-primary-100: #e0f2fe; + --color-primary-200: #bae6fd; + --color-primary-300: #7dd3fc; + --color-primary-400: #38bdf8; + --color-primary-500: #0ea5e9; + --color-primary-600: #0284c7; + --color-primary-700: #0369a1; + --color-primary-800: #075985; + --color-primary-900: #0c4a6e; + + /* OWL entity accents */ + --color-owl-class: #4ade80; + --color-owl-property: #60a5fa; + --color-owl-individual: #f472b6; + --color-owl-axiom: #fbbf24; + + /* Slate neutrals */ + --color-slate-50: #f8fafc; + --color-slate-100: #f1f5f9; + --color-slate-200: #e2e8f0; + --color-slate-300: #cbd5e1; + --color-slate-400: #94a3b8; + --color-slate-500: #64748b; + --color-slate-600: #475569; + --color-slate-700: #334155; + --color-slate-800: #1e293b; + --color-slate-900: #0f172a; + --color-slate-950: #020617; + + /* Semantic — light mode */ + --bg: #ffffff; + --bg-subtle: #f8fafc; + --bg-canvas: var(--color-slate-50); + --surface: #ffffff; + --surface-raised: #ffffff; + --border: var(--color-slate-200); + --border-strong: var(--color-slate-300); + --text: var(--color-slate-900); + --text-muted: var(--color-slate-500); + --text-faint: var(--color-slate-400); + --accent: var(--color-primary-600); + --accent-bg: var(--color-primary-50); + --accent-border: var(--color-primary-300); + --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04); + --shadow-md: 0 4px 12px rgba(15, 23, 42, 0.08); + --shadow-lg: 0 16px 32px rgba(15, 23, 42, 0.16); + + /* Typography */ + --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --font-mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace; + --text-xs: 0.75rem; + --text-sm: 0.875rem; + --text-base: 1rem; + --text-lg: 1.125rem; + --text-xl: 1.25rem; + --text-2xl: 1.5rem; + + /* Spacing */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-8: 32px; + + /* Shape */ + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 12px; + --radius-full: 9999px; + + /* Layout */ + --tree-width: 280px; + --detail-width: 420px; + --header-height: 56px; + --tabstrip-height: 44px; +} + +[data-theme="dark"] { + --bg: var(--color-slate-900); + --bg-subtle: var(--color-slate-950); + --bg-canvas: var(--color-slate-950); + --surface: var(--color-slate-800); + --surface-raised: var(--color-slate-800); + --border: var(--color-slate-700); + --border-strong: var(--color-slate-600); + --text: var(--color-slate-50); + --text-muted: var(--color-slate-400); + --text-faint: var(--color-slate-500); + --accent: var(--color-primary-400); + --accent-bg: rgba(14, 165, 233, 0.12); + --accent-border: var(--color-primary-700); + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45); + --shadow-lg: 0 16px 32px rgba(0, 0, 0, 0.55); +} + +* { + box-sizing: border-box; +} + +html, body { + margin: 0; + padding: 0; + background: var(--bg-canvas); + color: var(--text); + font-family: var(--font-sans); + font-size: var(--text-sm); + line-height: 1.5; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +button { + font-family: inherit; + font-size: inherit; + cursor: pointer; + background: transparent; + border: 0; + color: inherit; + padding: 0; +} + +a { + color: var(--accent); + text-decoration: none; +} + +/* Sketch toolbar — reused across all sketches */ +.sketch-tools { + position: fixed; + bottom: 12px; + right: 12px; + z-index: 9999; + font-family: system-ui, sans-serif; + font-size: 12px; + background: rgba(15, 23, 42, 0.85); + color: white; + padding: 8px 12px; + border-radius: 8px; + opacity: 0.5; + transition: opacity 0.2s ease; + display: flex; + gap: 12px; + align-items: center; +} +.sketch-tools:hover { opacity: 1; } +.sketch-tools button, +.sketch-tools select { + background: rgba(255, 255, 255, 0.1); + color: white; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + padding: 3px 8px; + font-size: 11px; +} +.sketch-tools button:hover { + background: rgba(255, 255, 255, 0.2); +} + +/* Variant tab nav */ +.variant-nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 9998; + background: var(--surface); + border-bottom: 1px solid var(--border); + padding: 8px 16px; + display: flex; + gap: 8px; + align-items: center; + font-family: system-ui, sans-serif; +} +.variant-nav .label { + font-size: 11px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-right: 8px; +} +.variant-tab { + font-size: 13px; + padding: 6px 12px; + border-radius: 6px; + color: var(--text-muted); + border: 1px solid transparent; + transition: all 0.15s ease; +} +.variant-tab:hover { + color: var(--text); + background: var(--bg-subtle); +} +.variant-tab.active { + background: var(--accent-bg); + color: var(--accent); + border-color: var(--accent-border); + font-weight: 500; +} + +body.with-variants { + padding-top: 56px; +} From bb7249630be618597b4334d02556d7d14791fef7 Mon Sep 17 00:00:00 2001 From: damienriehl Date: Sat, 2 May 2026 12:10:55 -0500 Subject: [PATCH 02/10] =?UTF-8?q?spec(phase-17):=20add=20SPEC.md=20for=20G?= =?UTF-8?q?raph=20as=20Entity-Scoped=20Tab=20in=20Detail=20Pane=20?= =?UTF-8?q?=E2=80=94=2011=20requirements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Locks WHAT/WHY for Phase 17 (v0.5.0). 11 falsifiable requirements derived from the design contract (.planning/notes/graph-as-entity-pane.md) + 2 rounds of Socratic refinement that pinned the cross-repo scope and verification floor. Key locked decisions surfaced in spec round 1-2: · Phase 17 spans both ontokit-web and ontokit-api (web restructure + api endpoint extension for property/individual focus IRIs) · Hard dependency on PR #88 (entity graph) merging to dev before Phase 17 implementation branch is cut · Verification floor: Vitest unit tests + AI-driven Chrome DevTools MCP smoke pass walking the success criteria in real browser before human UAT (no Playwright e2e in this phase) · Property graphs render full content per Decision 5: domain + range + parent properties + see-also (improvement over WebProtege's empty-island treatment) Ambiguity: 0.19 (gate ≤ 0.20). All 4 dimensions cleared their minimums. Tracker: ontokit-web#229 (under v0.5.0 milestone #214). Co-Authored-By: Claude Opus 4.7 --- .../17-SPEC.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-SPEC.md diff --git a/.planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-SPEC.md b/.planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-SPEC.md new file mode 100644 index 000000000..b3a0b9b97 --- /dev/null +++ b/.planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-SPEC.md @@ -0,0 +1,170 @@ +# Phase 17: Graph as Entity-Scoped Tab in Detail Pane — Specification + +**Created:** 2026-05-02 +**Ambiguity score:** 0.19 (gate: ≤ 0.20) +**Requirements:** 11 locked + +## Goal + +Both Standard and Developer views render a `Detail | Graph[ | Source]` tab strip at the topmost chrome of the right-side detail pane for all three entity types (class, property, individual). The Developer view's `Tree | Source | Graph` left-pane peer-tab strip is reduced to no peer strip (left pane shows only the tree). The Standard view's prior small Graph icon button in the entity-actions area is removed. The ` Source` link in entity-actions is removed in both views (replaced by the Source tab in Developer; not exposed in Standard). The full source view is reachable from the Source tab via a Modal ⇄ Maximize round-trip. + +## Background + +The graph today is misplaced: a peer-tab in Developer view (alongside Tree/Source — implies it's a *navigation surface*, not an entity-scoped view), and a small icon button in Standard view that users miss entirely. Source is similarly fragmented across a left-pane mode strip in Developer and a small ` Source` link in the entity actions of all detail panels. + +Codebase scout (commit `dev` HEAD prior to PR #88 merge): + +- `components/editor/standard/StandardEditorLayout.tsx` — 2-pane layout (tree | detail). Detail panels carry the Graph icon button + ` Source` link. +- `components/editor/developer/DeveloperEditorLayout.tsx` — 2-pane layout. Left pane has `Tree | Source` mode strip with `Graph` peer-tab. Right pane is the detail panel. +- `components/editor/{Class,Property,Individual}DetailPanel.tsx` — render entity-actions with Copy IRI + ` Source` button. +- `components/graph/OntologyGraph.tsx` — current client-side renderer (PR #88 replaces with server-side BFS + new `useELKLayout`, `OntologyEdge`). +- `components/graph/EntityGraphModal.tsx` — existing modal frame; reused for graph and (new) for full-source modal. +- `lib/stores/selectionStore.ts` — Zustand store with `iri`, `type`, `mode` fields (extended in #228). This phase adds `activePaneTab: 'detail' | 'graph' | 'source' | null`. +- `lib/api/graph.ts` (ontokit-web) and `ontokit-api`'s `/api/v1/projects/{id}/ontology/classes/graph` endpoint — class-only today. Phase 17 extends API for properties and individuals (see Requirement 11). + +Design contract (decisions 1–11): `.planning/notes/graph-as-entity-pane.md`. Sketch winners: `.planning/sketches/{001..004}/index.html`. + +## Requirements + +1. **Tab strip — right pane**: A unified tab strip becomes the topmost chrome of the right-pane detail panel in both Standard and Developer views. + - Current: No tab strip exists. Standard panel goes pip+name+IRI → form fields. Developer panel same. Graph affordances are scattered. + - Target: Right pane renders `` above the entity header. Tabs are `Detail | Graph` (Standard, 50/50) or `Detail | Graph | Source` (Developer, 33/33/33). Visual treatment: icon + sentence-case label, `flex: 1 1 0`, accent underline + accent-tinted background fill on active, 8px vertical padding + text-xs (matches left tree-tabs height). + - Acceptance: Both views show the strip at the top of the right pane; clicking a tab swaps the body content beneath the entity header; entity header (pip + name + IRI + Copy IRI button) stays visible across tab swaps; bottom border of the right-pane strip aligns horizontally with the bottom border of the left-pane `Classes | Properties | Individuals` strip. + +2. **Old peer-tab Graph removed (Developer view)**: The left-pane `Tree | Source | Graph` peer-tab strip is reduced to no peer-tab strip. + - Current: `DeveloperEditorLayout` renders `Tree | Source | Graph` peer-tabs as the left-pane mode selector. + - Target: The `Tree | Source` strip is removed entirely. The left pane shows only the tree (with `Classes | Properties | Individuals` sub-tabs). Source moves to the right-pane Source tab (Requirement 7). + - Acceptance: No Tree/Source/Graph mode selector renders anywhere in the left pane in Developer view. The left pane's only tab strip is `Classes | Properties | Individuals`. + +3. **Old Graph icon button removed (Standard view)**: The small Graph icon button in the entity-actions area is removed. + - Current: `Class/Property/IndividualDetailPanel` render ``. When `size === 'maximized'` AND `cameFromModal` → ``. Note: Lucide `Minimize2` icon is used for the "Restore" button per Windows convention (the icon maps to the action of returning to a smaller windowed size, despite its lucide name). + +### Annotation-property empty graph state (new — inside `OntologyGraph.tsx` or its parent) + +- Trigger: response is `{nodes: [focus_only], edges: []}` (LOCKED — SPEC Requirement 11) +- Layout: centered in graph viewport +- Icon: Lucide `Tag` (`h-8 w-8 text-slate-400 mb-3`) — annotation properties are tags +- Heading: "No relationships to display" (`text-sm font-semibold text-slate-700 dark:text-slate-300`) +- Body: "This annotation property has no domain, range, parent, or see-also relationships to render." (`text-xs text-slate-500 dark:text-slate-400 max-w-md`) + +### Reused (no modification this phase, except the rename in D-09): + +- `Button` from `components/ui/button` — toolbar actions in Source tab +- `OntologyGraph`, `OntologyNode` — extended for new node types (property, individual, annotation-property focus) but visual API unchanged +- `OntologyEdge` — extended palette per the new `edge_kind` values declared above +- Existing Monaco editor infrastructure (`TurtleEditor.tsx` + Web Worker IRI index) + +### Removed (LOCKED — SPEC Requirements 2, 3, 4): + +- Developer view's left-pane `Tree | Source | Graph` mode strip — entire strip deleted +- Standard view's small Graph icon button in entity-actions of all three detail panels +- ` Source` link button in entity-actions of all three detail panels (both views) + +--- + +## Interaction States + +### TabStrip + +- **Default**: Detail tab active on first session load (LOCKED — SPEC Requirement 8) +- **Click**: swaps body content; updates `useSelectionStore.activePaneTab`; entity header persists +- **Keyboard**: Left/Right arrow keys move focus between tabs (standard ARIA tablist pattern); Home/End jump to first/last; Enter or Space activates the focused tab +- **Selection change while tab active**: tab stays active across entity selection (LOCKED — Requirement 6); body content re-renders for the new entity +- **Mode change (Standard ⇄ Developer) while Source tab active**: derived fallback via `useEffectiveTab(editorMode)` returns `'detail'` when active is `'source'` and mode is `'standard'`; store value is **never mutated** (LOCKED — D-15). Switching back to Developer restores Source tab. +- **Hover**: inactive tabs gain `hover:bg-slate-50 dark:hover:bg-slate-800/50` + label color shift; active tab has no hover change (already filled) +- **Focus-visible**: 2px primary ring inset (does not displace the bottom-border alignment) + +### Source-tab toolbar + +- **`↗ Open full source` click**: opens `EntityModal` at modal size; sets focus inside the modal (focus trap kicks in) +- **`⎘ Copy snippet` click**: copies snippet text to clipboard; shows toast "Snippet copied" (existing toast system) +- **Hover on `↗ Open full source`**: `bg-primary-50 dark:bg-primary-900/20`, label stays primary-600/400 +- **Hover on `⎘ Copy snippet`**: `bg-slate-100 dark:bg-slate-800`, label slate-700/300 + +### Full-source overlay (Modal ⇄ Maximize ⇄ Restore round-trip) + +- **Open**: modal size, `cameFromModal: false`, no Restore button visible. Focus moves into modal. +- **`⤢ Maximize`**: size → `'maximized'`, `cameFromModal: true`, `data-overlay-takeover="true"` on `` → tree + detail panes hide via CSS. Restore button now visible. +- **`⊟ Restore`**: size → `'modal'`, `cameFromModal` stays `true` (so Restore appears again on next Maximize). `data-overlay-takeover` cleared. +- **Bounce maximize ⇄ restore**: symmetric, scroll position preserved (D-11 — Monaco component never unmounts). +- **Esc / click-outside / ✕**: from any size → fully closes; `cameFromModal` resets to `false`. (LOCKED — D-12.) +- **Selection change while overlay open**: overlay does NOT dismiss; body scrolls to new entity's lines, highlight re-applies (LOCKED — Requirement 10). +- **Tab persistence**: opening / closing / maximizing / restoring the overlay does NOT change `activePaneTab` (D-10). + +### Empty states + +- **Annotation property with no relationships** (Graph tab): centered icon + heading + body; backend returns `{nodes: [focus_only], edges: []}` (LOCKED — Requirement 11). +- **Source extractor cannot locate entity** (Source tab): centered icon + heading + body; toolbar still shows `↗ Open full source` so user can verify in full file (D-02). + +### Reduced motion / high contrast + +- All transitions on TabStrip (`transition-colors`) and modal open are suppressed under `@media (prefers-reduced-motion: reduce)` per existing `app/globals.css` rule. +- Focus ring widens under `@media (prefers-contrast: more)` per existing `app/globals.css` rule. + +### Animation + +- TabStrip active-state change: `transition-colors duration-150` (matches `EntityTabBar.tsx` precedent). +- Modal open: existing `EntityGraphModal` animation (fade + scale; not changed in this phase per D-09 + SPEC out-of-scope). +- Maximize ⇄ Restore: **no transition** — instantaneous size swap. CSS attribute toggle is intentionally abrupt to make the round-trip feel like a window-management action, not a graceful transform. +- Selection change while overlay open: Monaco's native scroll-to-line is the only motion; no fade. + +--- + +## Copywriting Contract + +| Element | Copy | +|---------|------| +| Tab label: Detail | Detail | +| Tab label: Graph | Graph | +| Tab label: Source | Source | +| Source toolbar: line comment | `# File: ontology.ttl, line {N}` (literal — LOCKED via SPEC Requirement 7 + D-03) | +| Source toolbar: primary action | Open full source | +| Source toolbar: secondary action | Copy snippet | +| Source toolbar: copy-success toast | Snippet copied | +| Modal title | Source — {entityLocalName} | +| Modal: Maximize aria-label | Maximize source view | +| Modal: Close aria-label | Close source view | +| Takeover: Restore aria-label | Restore to modal view | +| Takeover: Close aria-label | Close source view | +| Annotation-property empty graph: heading | No relationships to display | +| Annotation-property empty graph: body | This annotation property has no domain, range, parent, or see-also relationships to render. | +| Source extractor empty state: heading | Source not available for this entity | +| Source extractor empty state: body | It may be defined in an imported ontology. | +| Empty state heading (no entity selected, applies to all tabs) | Select an entity to begin | +| Empty state body (no entity selected) | Pick a class, property, or individual from the tree to view its details, graph, or source. | +| Error state: graph load failure | Failed to load graph. {error.message} — Try selecting the entity again. | +| Error state: source load failure | Failed to load source. {error.message} — Try refreshing the page. | +| Error state: copy snippet failure | Couldn't copy snippet. Try again. | + +No destructive actions in this phase — Source tab is read-only, graph is read-only (LOCKED — out of scope). + +--- + +## Accessibility + +- **TabStrip**: `role="tablist"` on container; each tab `role="tab"`, `aria-selected`, `aria-controls` pointing at its panel id, `id` for the panel's `aria-labelledby`. Body panels are `role="tabpanel"` with `aria-labelledby` pointing at their tab. +- **Keyboard**: Left/Right arrows move focus between tabs; Home/End jump first/last; Enter or Space activates the focused tab. Tab key moves out of the tablist into the panel body (no roving tab-stop traps the user). +- **Active descendant**: focus-visible ring (2px primary) shows which tab has keyboard focus distinct from which tab is active (`aria-selected`). +- **EntityModal**: `role="dialog"`, `aria-modal="true"`, `aria-labelledby` on the modal title; focus trap (existing — Radix Dialog); Esc dismisses. +- **Maximize / Restore buttons**: icon-only, `aria-label` per the copy table. Tooltip via existing tooltip pattern (title attribute or Radix Tooltip — match existing `EntityGraphModal` convention). +- **Source tab toolbar buttons**: `aria-label` on icon-only buttons; `Open full source` button has visible label, no aria override needed. +- **Screen-reader announcement on tab change**: tab swap is conveyed by `aria-selected` + focus move; an explicit `aria-live` is NOT added for tab changes (would create double announcements with most screen readers). +- **Screen-reader announcement on selection change while Source tab active** (auto-jump per Requirement 10): existing `useAnnounce()` hook from `ScreenReaderAnnouncer` fires "Source updated to {entity local name}" politely. +- **Annotation-property empty state**: heading uses `

` semantic level (the parent panel uses `

` for entity name). +- **Reduced motion**: see Animation section. +- **High contrast**: see Color section + existing `app/globals.css` rule. + +--- + +## Registry Safety + +| Registry | Blocks Used | Safety Gate | +|----------|-------------|-------------| +| shadcn official | none (no shadcn initialized) | not applicable | +| Third-party | none declared | not applicable | + +No third-party registries. All components use direct Radix UI primitives + lucide-react + project conventions. All new components are written in-repo (`PaneTabStrip`, `SourceTabBody`, `FullSourceOverlay`, `useFullSourceOverlay`, renamed `EntityModal`). + +--- + +## Pre-Populated From + +| Source | Decisions Used | +|--------|---------------| +| SPEC.md (LOCKED) | Tab strip placement (Requirement 1), bottom-border alignment constraint, density (8px py + text-xs), 50/50 + 33/33/33 widths, default Detail tab, Source tab Developer-only, Modal ⇄ Maximize ⇄ Restore round-trip with `cameFromModal` semantics, removal of old Graph icon button + ` Source` link, click semantics preserved, tab persistence, auto-jump on selection change | +| CONTEXT.md (USER DECISIONS) | EntityModal rename (D-09), `useFullSourceOverlay` hook in local state (D-10), CSS-toggle takeover via `data-overlay-takeover` (D-11), Esc dismisses fully (D-12), `activePaneTab` on selectionStore non-persist (D-13/14), derived fallback via `useEffectiveTab` (D-15), no URL deep-linking (D-16), single `/entity-graph` endpoint with `focus_type` (D-05), single `edges[]` with `edge_kind` enum (D-06), frontend snippet extraction via generalized `findBlock` (D-01), empty-state copy approach (D-02), line-comment format (D-03), memoized snippet (D-04) | +| Sketches (LOCKED winners) | 001-D (icon + sentence-case label, ABOVE entity header); 002-D (compact 8px+text-xs, full-width); 003 applied (3-tab Developer); 004-C (~92% modal + Maximize⇄Restore) | +| Existing code | `EntityTabBar.tsx` density and Tailwind classes (parity for bottom-border alignment); `OntologyEdge.tsx` palette philosophy; `app/globals.css` reduced-motion + high-contrast rules; `lib/utils.ts` `cn()` helper | +| Phase 16 UI-SPEC | Color tokens (60/30/10), semantic palette pattern, copywriting style, accessibility patterns | +| Claude's discretion (CONTEXT) | TabStrip file location (`components/editor/PaneTabStrip.tsx`), final empty-state copy strings, edge palette specifics for new `edge_kind` values, Lucide icon picks (`FileText`, `Network`, `FileCode2`, `Tag`, `FileQuestion`, `Maximize2`, `Minimize2`) | + +--- + +## Checker Sign-Off + +- [ ] Dimension 1 Copywriting: PASS +- [ ] Dimension 2 Visuals: PASS +- [ ] Dimension 3 Color: PASS +- [ ] Dimension 4 Typography: PASS +- [ ] Dimension 5 Spacing: PASS +- [ ] Dimension 6 Registry Safety: PASS + +**Approval:** pending From 68629941e9b45ddd6dcc9a227760ff36587c1518 Mon Sep 17 00:00:00 2001 From: damienriehl Date: Sat, 2 May 2026 12:54:20 -0500 Subject: [PATCH 06/10] docs(state): record phase 17 ui-spec session .planning/STATE.md --- .planning/STATE.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.planning/STATE.md b/.planning/STATE.md index 4a4d3a40b..04a43977e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,8 +3,8 @@ gsd_state_version: 1.0 milestone: v0.4.0 milestone_name: LLM-Assisted Ontology Improvements status: completed -stopped_at: Phase 17 context gathered -last_updated: "2026-05-02T17:41:44.766Z" +stopped_at: Phase 17 UI-SPEC approved +last_updated: "2026-05-02T17:54:20.118Z" last_activity: 2026-04-08 progress: total_phases: 11 @@ -209,6 +209,6 @@ None yet. ## Session Continuity -Last session: 2026-05-02T17:41:44.760Z -Stopped at: Phase 17 context gathered -Resume file: .planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-CONTEXT.md +Last session: 2026-05-02T17:54:20.113Z +Stopped at: Phase 17 UI-SPEC approved +Resume file: .planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-UI-SPEC.md From a4d8dc6a849c2ba08e82ca72910eadd2d7ddacff Mon Sep 17 00:00:00 2001 From: damienriehl Date: Sat, 2 May 2026 13:11:46 -0500 Subject: [PATCH 07/10] docs(phase-17): add validation strategy .planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-VALIDATION.md --- .../17-RESEARCH.md | 916 ++++++++++++++++++ .../17-VALIDATION.md | 89 ++ 2 files changed, 1005 insertions(+) create mode 100644 .planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-RESEARCH.md create mode 100644 .planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-VALIDATION.md diff --git a/.planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-RESEARCH.md b/.planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-RESEARCH.md new file mode 100644 index 000000000..d15504167 --- /dev/null +++ b/.planning/phases/17-graph-as-entity-scoped-tab-in-detail-pane/17-RESEARCH.md @@ -0,0 +1,916 @@ +# Phase 17: Graph as Entity-Scoped Tab in Detail Pane — Research + +**Researched:** 2026-05-02 +**Domain:** React 19 / Next.js 15 frontend restructure (TabStrip + modal round-trip + Source-tab) + cross-repo FastAPI/RDFLib backend extension (entity-graph BFS for property + individual focus) +**Confidence:** HIGH (most upstream code already exists in repo; SPEC + UI-SPEC + 11 design decisions are locked) + +## Summary + +Phase 17 is mostly an **integration phase, not a research-heavy phase**. SPEC, CONTEXT, and UI-SPEC are pre-locked with 11 requirements + 16 implementation decisions + a full visual contract. The remaining unknowns are concrete code locations, prop contracts of components landing via PR #88, and a small set of generalization questions (findBlock, edge palette, ARIA tablist wiring). + +Scout findings: +- `findBlock` in `lib/ontology/turtleUtils.ts` is **already type-agnostic** — it matches subject IRIs by Turtle form, not by `owl:Class`-specific assertions. CONTEXT D-01 ("generalize findBlock") is partially obsolete; the function already handles property and individual subjects. The single landmine to preserve is the `prevLine.endsWith(";")||","` continuation-line guard at lines 207–211 / 233–235 / 265–268 — already implemented at all three fallback layers. +- `EntityTabBar.tsx` (the existing left-pane `Classes|Properties|Individuals` strip) is the canonical Tailwind density precedent (`flex-1 px-3 py-2 text-xs font-medium`). The new `PaneTabStrip` MUST use these exact classes for the bottom-border alignment constraint. +- PR #88's `EntityGraphModal` is currently a **graph-specific modal**. It needs renaming to `EntityModal` AND prop generalization (`children`, `headerExtras`, `size`, `onMaximize`, `onRestore`) — this is part of Phase 17, not pre-existing. +- PR #88's `lib/api/graph.ts` calls `/api/v1/projects/{id}/ontology/classes/graph` with `class_iri=...` (class-only). Phase 17 introduces a new `/entity-graph?focus_iri=...&focus_type=...` endpoint and refactors the client to call it. +- `useGraphData` was completely rewritten by PR #88 — current `dev` HEAD has the legacy client-side BFS version. Phase 17 builds on PR #88's server-side BFS hook, so any plan task touching `useGraphData` must base off PR #88's version. +- `lib/editor/indexWorker.ts` already builds an IRI → `{line, col, len}` index for ANY subject (class/property/individual), keyed by full IRI. Phase 17's `# File: ontology.ttl, line N` comment can read this directly via `sourceIriIndex` (already plumbed through `DeveloperEditorLayout`). +- Backend (`ontokit-api`): `ontokit/services/ontology.py:365` `build_entity_graph()` exists and rejects non-class IRIs at line 397 with `if (class_uri, RDF.type, OWL.Class) not in graph: return None`. Phase 17 needs to extend this method (or add sibling methods) to handle property + individual focus, plus rename the route to `/entity-graph` with a `focus_type` discriminator. + +**Primary recommendation:** Treat Phase 17 as **three plans**: +1. **API plan (ontokit-api)**: extend `build_entity_graph` to handle property/individual focus, add `focus_type` query param, expose new `/entity-graph` route, deprecate `/classes/graph` as an internal delegating shim, add `edge_kind` enum extension, add tests. +2. **Web plumbing plan (ontokit-web)**: rename `EntityGraphModal` → `EntityModal` with new slot props, add `PaneTabStrip` component, add `activePaneTab` to `selectionStore`, add `useEffectiveTab` derivation, add `useFullSourceOverlay` hook, extract `extractEntitySnippet` from `findBlock`, extend `OntologyEdge` palette for new `edge_kind` values, refactor `lib/api/graph.ts` to call `/entity-graph`. +3. **Web layout plan (ontokit-web)**: integrate `PaneTabStrip` into both layouts, remove old peer-tab strip + Graph icon button + ` Source` link, wire `SourceTabBody` + `FullSourceOverlay`, ensure tab persistence + auto-jump behavior, run AI smoke test, fix regressions. + +## Architectural Responsibility Map + +| Capability | Primary Tier | Secondary Tier | Rationale | +|------------|-------------|----------------|-----------| +| TabStrip rendering / state-bind | Browser/Client | — | Pure UI state; lives in component + zustand selectionStore | +| `activePaneTab` persistence (session-only) | Browser/Client | — | D-13/14: non-persist Zustand; resets on reload by design | +| Source snippet extraction | Browser/Client | — | D-01/04: already-loaded source text + memoized findBlock; no API call | +| Full-source overlay | Browser/Client | — | D-10/11: local useState hook + CSS attribute toggle on layout root | +| Maximize/Restore CSS takeover | Browser/Client | — | D-11: `[data-overlay-takeover="true"]` on `` hides `.editor-tree-pane`/`.editor-detail-pane` | +| Entity-graph BFS (class/property/individual) | API/Backend | — | RDFLib graph traversal; SPEC R11 | +| Edge_kind enum (10 values) | API/Backend → Browser | — | Backend emits, frontend renders palette per UI-SPEC §Color | +| Class-graph backward compat | API/Backend | — | D-05: `/classes/graph` delegates to `/entity-graph` during deprecation window | +| Auto-jump source on selection change | Browser/Client | — | SPEC R10: existing `pendingScrollIri` plumbing already in `DeveloperEditorLayout` | + +## Standard Stack + +### Core (already in repo — versions verified from `package.json` 2026-05-02) +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| Next.js | ^16.2.4 | App framework | [VERIFIED: package.json] | +| React | ^19.0.0 | UI runtime | [VERIFIED: package.json] | +| TypeScript | (project default) | Type safety | [VERIFIED: tsconfig present] | +| Tailwind CSS | 3.x | Styling | [VERIFIED: tailwind.config.ts present, darkMode: "class"] | +| @xyflow/react | ^12.10.1 | Graph rendering | [VERIFIED: package.json] | +| elkjs | ^0.11.0 | Graph layout | [VERIFIED: package.json] | +| zustand | ^5.0.0 | Client state | [VERIFIED: package.json] | +| @radix-ui/react-dialog | ^1.1.0 | Modal primitive (focus trap, dismissal) | [VERIFIED: package.json] | +| @radix-ui/react-tabs | ^1.1.0 | Available but NOT used here per UI-SPEC | [VERIFIED: package.json — UI-SPEC chooses native ARIA tablist for store-driven external control, matching `EntityTabBar` and Phase 16 `ShardTabNavigator`] | +| lucide-react | ^1.14.0 | Icons | [VERIFIED: package.json] | +| Monaco Editor | (via existing TurtleEditor) | Source editing | [VERIFIED: components/editor/TurtleEditor.tsx present] | +| Vitest | ^4.0.18 | Unit tests | [VERIFIED: package.json] | + +### Supporting +| Library | Version | Purpose | When to Use | +|---------|---------|---------|-------------| +| @testing-library/react | (existing) | RTL render/fireEvent for unit tests | Test new components per `__tests__/components/editor/standard/EntityTabBar.test.tsx` precedent [VERIFIED: file exists] | +| MCP chrome-devtools | (Claude Code MCP) | AI smoke testing | SPEC mandate; `/snap/bin/chromium` headless explicitly disallowed per MEMORY | + +### Alternatives Considered +| Instead of | Could Use | Tradeoff | +|------------|-----------|----------| +| Native ARIA tablist | `@radix-ui/react-tabs` | Radix Tabs ships internal state; Phase 17 needs external `activePaneTab` from `selectionStore` to drive selection. UI-SPEC explicitly rejects Radix Tabs for this reason, mirroring Phase 16's `ShardTabNavigator` precedent. [CITED: 17-UI-SPEC.md §Design System] | +| Modal as new component | Reuse `EntityGraphModal` (renamed) | D-09: rename + slot generalization keeps backdrop / focus-trap / dismissal logic in one place. | +| `display: none` for takeover | Visibility:hidden / absolute offscreen | D-11 picks CSS attribute toggle (`[data-overlay-takeover="true"] .editor-tree-pane { display: none }`). See Common Pitfall 1 for Monaco scroll-preservation analysis. | + +**Installation:** No new dependencies needed. All required packages are already in `package.json`. + +**Version verification:** +```bash +# Run from ontokit-web/ (already verified 2026-05-02): +node -p "require('./package.json').dependencies['@radix-ui/react-dialog']" # ^1.1.0 +node -p "require('./package.json').dependencies['lucide-react']" # ^1.14.0 +``` + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|------------------| +| R1 | TabStrip becomes topmost chrome of right pane (Detail/Graph or Detail/Graph/Source) | UI-SPEC §Component Inventory locks Tailwind classes; `EntityTabBar.tsx` is the parity precedent | +| R2 | Old peer-tab `Tree\|Source\|Graph` strip removed (Developer view) | `DeveloperEditorLayout.tsx:373-417` defines the strip; remove entirely. `viewMode` state at line 246 must be repurposed (Source moves to right-pane tab). See Q8 below | +| R3 | Old Graph icon button removed (Standard view entity-actions) | `StandardEditorLayout.tsx:462-471` renders the `` button; delete | +| R4 | Old ` Source` link removed (both views, all 3 detail panels) | `ClassDetailPanel.tsx:608-617` (verified). Property + Individual panels: needs grep verification — see Q8 | +| R5 | Click semantics preserved (single = recenter, double = select) | PR #88's `OntologyGraph.tsx` already implements this; preserve when graph renders inside Graph tab | +| R6 | `activePaneTab` persists across selection changes | D-13: add field to `selectionStore`. Current store at `lib/stores/selectionStore.ts:12-22` has `iri`/`type`/`mode`; extend | +| R7 | Source as Developer-only third tab with snippet + toolbar | New `SourceTabBody` component; reuse `Monaco TurtleEditor` in readOnly mode | +| R8 | Default tab on first load = Detail | D-14: non-persist Zustand defaults to `'detail'` (or `null` → fallback) | +| R9 | Modal ⇄ Maximize ⇄ Restore round-trip | D-09/10/11/12: new `useFullSourceOverlay` hook + renamed `EntityModal` slot props | +| R10 | Tree → Source auto-jump on selection change | Existing `pendingScrollIri` plumbing in `DeveloperEditorLayout` (lines 153, 365–368, 642–643) — preserve | +| R11 | Backend supports class + property + individual focus | `ontokit-api` `build_entity_graph` at `ontokit/services/ontology.py:365`; route at `ontokit/api/routes/projects.py:660-701` | + +## Architecture Patterns + +### System Architecture Diagram + +``` + [User clicks tab in PaneTabStrip] + ↓ + useSelectionStore.setActivePaneTab(tab) + ↓ + ┌─────────── activePaneTab ───────────┐ + ↓ ↓ ↓ + 'detail' 'graph' 'source' + ↓ ↓ ↓ + [Class/Property/ [OntologyGraph [SourceTabBody] + IndividualDetail- with focusIri] ↓ + Panel body] ↓ ┌──────┴──────┐ + ↓ ↓ ↓ + graphApi.getEntityGraph() Monaco snippet ↗ Open full + ↓ via extract- source btn + [server-side BFS: EntityBlock( ↓ + class | property | source, iri) useFullSource- + individual] Overlay.open() + ↓ ↓ + {nodes, edges with ┌────── EntityModal (renamed) ──────┐ + edge_kind ∈ 10 vals} │ = FullSourceOverlay │ + ↓ │ (full Monaco at ~92vw/92vh) │ + OntologyEdge palette │ = ⤢Max / ⊟Restore │ + maps edge_kind → color │ buttons │ + └─────────────────────────────────────┘ + ↓ + ⤢ Maximize → setattr + data-overlay-takeover="true" + → CSS hides .editor-tree-pane + + .editor-detail-pane + ⊟ Restore → unset attr (Monaco never + unmounts; scroll preserved) + + + Backend (ontokit-api): + GET /api/v1/projects/{id}/ontology/entity-graph?focus_iri=...&focus_type=class|property|individual + ↓ + OntologyService.build_entity_graph(focus_iri, focus_type) + ↓ + ┌──────────────┼──────────────┐ + ↓ ↓ ↓ + focus_type= focus_type= focus_type= + class property individual + (existing (NEW: domain, (NEW: rdf:type, + BFS up/down) range, sub- object props, + PropertyOf, sameAs, + seeAlso) seeAlso) + ↓ + {nodes, edges with edge_kind enum} + ↓ + Old route /classes/graph → delegates to /entity-graph (D-05 deprecation) +``` + +### Recommended File Placement +``` +ontokit-web/ +├── components/editor/ +│ ├── PaneTabStrip.tsx # NEW (sibling of EntityTabBar) +│ ├── SourceTabBody.tsx # NEW +│ ├── FullSourceOverlay.tsx # NEW +│ ├── ClassDetailPanel.tsx # MODIFY: remove Source button (lines 608-617) +│ ├── PropertyDetailPanel.tsx # MODIFY: remove Source + (any) Graph button +│ ├── IndividualDetailPanel.tsx # MODIFY: remove Source + (any) Graph button +│ ├── developer/DeveloperEditorLayout.tsx # MODIFY: remove Tree|Source|Graph mode strip; integrate PaneTabStrip +│ └── standard/StandardEditorLayout.tsx # MODIFY: remove Graph icon button (lines 462-471); integrate PaneTabStrip +├── components/graph/ +│ └── EntityGraphModal.tsx → EntityModal.tsx # RENAME + prop generalization (D-09) +├── lib/ +│ ├── api/graph.ts # MODIFY (PR #88 base): refactor getEntityGraph for /entity-graph + focus_type +│ ├── graph/types.ts # MODIFY: extend GraphEdgeType with 6 new values +│ ├── hooks/useFullSourceOverlay.ts # NEW +│ ├── hooks/useGraphData.ts # MODIFY (PR #88 base): pass focus_type through +│ ├── ontology/extractEntitySnippet.ts # NEW (or reuse findBlock + slice) +│ └── stores/selectionStore.ts # MODIFY: add activePaneTab + setter +├── components/graph/OntologyEdge.tsx # MODIFY: extend edgeTypeConfig with 6 new edge_kind entries +└── __tests__/ # NEW unit tests for: PaneTabStrip, SourceTabBody, useFullSourceOverlay, EntityModal slot wiring, selectionStore.activePaneTab, edge palette extension + +ontokit-api/ +├── ontokit/api/routes/projects.py # MODIFY: add new /entity-graph route; mark /classes/graph deprecated (delegate) +├── ontokit/services/ontology.py # MODIFY: extend build_entity_graph to handle property + individual focus +├── ontokit/services/entity_graph_helpers.py # MODIFY: add property_neighbors() + individual_neighbors() helpers +├── ontokit/schemas/graph.py # MODIFY: extend GraphEdgeType Literal with 6 new values +└── tests/unit/test_entity_graph.py # MODIFY: add property + individual + annotation-property focus tests +``` + +### Pattern 1: Native ARIA tablist with external state control (Phase 16 precedent) +**What:** TabStrip uses `role="tablist"` + ` + ))} + +``` + +Keyboard nav (Left/Right/Home/End/Enter) is handled by an `onKeyDown` on the tablist container, walking `tabs` index and updating focus + calling `onTabChange`. [CITED: WAI-ARIA Authoring Practices — Tabs Pattern] + +### Pattern 2: CSS attribute-toggled takeover (D-11) +**What:** Maximize sets `document.documentElement.dataset.overlayTakeover = "true"`. CSS rule (in `app/globals.css` or scoped per layout) hides the tree + detail panes: +```css +[data-overlay-takeover="true"] .editor-tree-pane, +[data-overlay-takeover="true"] .editor-detail-pane { + display: none; +} +[data-overlay-takeover="true"] .editor-overlay-modal { + position: fixed; inset: 0; + width: 100vw; height: 100vh; + border-radius: 0; + max-width: none; +} +``` +**When to use:** When a child overlay needs to expand beyond a portal modal but the underlying component (Monaco) MUST keep its scroll position. Unmount/remount strategies destroy Monaco's view state. +**Why:** Monaco's `IEditor` instance is tied to its DOM container. Hiding via `display: none` on an *ancestor* element preserves the editor instance — when restored, Monaco re-uses its existing scroll position. Hiding the editor's container directly (or unmounting it) discards view state. [VERIFIED: PR #88's `OntologySourceEditor.tsx` uses `monaco.editor.IStandaloneCodeEditor` instance, which preserves view state across DOM hide/show as long as the instance is not disposed] + +### Pattern 3: Useful Memo for snippet extraction (D-04) +```tsx +// Source: D-04 specification +const snippet = useMemo(() => { + if (!sourceContent || !focusIri) return null; + return extractEntitySnippet(sourceContent, focusIri); +}, [sourceContent, focusIri]); +``` +`extractEntitySnippet` wraps the existing `findBlock` (re-exported from `lib/ontology/turtleUtils.ts`), slices `lines[startLine..endLine]`, joins with `\n`, returns `{snippet, startLine}` so the toolbar can render `# File: ontology.ttl, line ${startLine + 1}`. (`startLine` is 0-indexed in `findBlock`; the comment uses 1-indexed lines per `IriPosition.line` convention in `indexWorker.ts:28`.) + +### Anti-Patterns to Avoid +- **Don't unmount Monaco on Maximize/Restore.** Use CSS attribute toggle only (D-11). Unmounting destroys scroll position even if you save the line number — Monaco's word wrap, fold state, and selection are all lost. +- **Don't mutate `activePaneTab` on mode change.** D-15: derive `useEffectiveTab(editorMode)` at render time. Mutation breaks the user's preference (Developer→Standard→Developer should restore Source tab). +- **Don't add a feature flag or try/catch fallback for the new `/entity-graph` endpoint.** D-08: sequential cross-repo merge order — api ships first to dev, then web cuts from dev. No graceful degradation; if API isn't deployed, web breaks (and that's fine — it's a coordinated release). +- **Don't re-parse the full Turtle source for line numbers.** The Web Worker IRI index already maps every subject IRI to `{line, col, len}`. Read it from `sourceIriIndex` (already a `Map` plumbed through `DeveloperEditorLayout` — lines 91, 152, 366). + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| Subject-block discovery in Turtle | New regex per entity type | Existing `findBlock` from `lib/ontology/turtleUtils.ts:178-277` | Already type-agnostic. Handles full IRI, prefixed names, relative IRIs (via @base), continuation-line guards. The "class-only" misimpression comes from `turtleClassUpdater.ts` — but `findBlock` itself is generic. | +| IRI → line number lookup | Re-parse Turtle in component | `sourceIriIndex: Map` from `lib/editor/indexWorker.ts` | Already runs in Web Worker, already plumbed through the editor layouts. | +| Modal focus trap + dismissal | Custom focus management | PR #88's `EntityGraphModal` (renamed) — keeps existing focus trap at `EntityGraphModal.tsx:34-83` | Don't reimplement; just generalize props. | +| Tab keyboard nav | Custom focus rover | Native ARIA tablist pattern + `tabIndex={activeTab === tab.id ? 0 : -1}` | Standard pattern; matches Phase 16 `ShardTabNavigator`. | +| BFS over RDF graph | Custom traversal | `OntologyService.build_entity_graph` at `ontokit/services/ontology.py:365` | Extend, don't rewrite. The `_classify_node` function at line 437-455 already detects properties + individuals — the gating is at line 397 (`if (class_uri, RDF.type, OWL.Class) not in graph: return None`). | +| Annotation-property "no relationships" detection | Custom check on response | Backend returns `{nodes: [focus_only], edges: []}` per D-05/SPEC R11; frontend checks `edges.length === 0 && nodes.length === 1` | Single source of truth in API. | + +**Key insight:** Phase 17 is mostly a **wiring + rename + extend** phase. The platform already has every primitive needed; the planner's job is to sequence the changes correctly across two repos. + +## Per-Question Findings + +### Q1. TabStrip ARIA tablist wiring +**Resolved.** UI-SPEC §Accessibility lines 312-323 fully specify the pattern. Implementation: +- Container: `
` +- Each tab: `