Skip to content

Commit 3d70662

Browse files
committed
feat: Sprint W-0 — Sidebar reorg mode with inline category/entity reordering
Add a toggle button in the sidebar (Owner-only) that activates "reorg mode" for inline drag-to-reorder of categories and entities. Category reorder uses the existing PUT /sidebar-config API; entity reorder uses PUT /entities/:eid/reorder. - Reorg toggle button next to "Categories" header (grip icon → check when active) - Category D&D with drag handles, visibility toggles (eye icons), and save - Entity D&D now conditional on reorg mode (was always active) - Touch support for mobile D&D (touchstart/touchmove/touchend with ghost element) - Auto-exit reorg mode on navigation or drill state change - CSS styles for drop targets, drag handles, and mode indicators - Updated relations/.ai.md with graph visualization features from V-4 - data-entity-type-id attribute on category links for JS identification https://claude.ai/code/session_01QJLkgjQDu5qohzJKGV4hj9
1 parent 9587098 commit 3d70662

8 files changed

Lines changed: 753 additions & 11 deletions

File tree

.ai/status.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
<!-- ====================================================================== -->
99

1010
## Last Updated
11+
2026-03-10 -- **Sprint W-0: Nav Menu Reorg Mode (IN PROGRESS).**
12+
13+
24. Starting W-0: Sidebar reorg mode toggle button, inline category reorder, conditional entity drag-and-drop, touch support for mobile. Primarily frontend work — existing APIs (`PUT /sidebar-config`, `PUT /entities/:eid/reorder`) already support reordering. Also updating stale relations widget documentation.
14+
15+
### Previous Update
1116
2026-03-10 -- **Sprint V-4: Enhanced Graph View & Cover Images (COMPLETE).**
1217

1318
18. **@Mention edges in graph**: `FindAllMentionLinks()` in entity repository scans `entry_html` for `data-mention-id` attributes across a campaign. `MentionLink` model. `GetMentionLinks()` service method. `MentionLinkProvider` interface bridges entities→relations without circular imports. Mention edges appear as dashed purple lines in the D3 graph.

.ai/todo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ _Quick capture, backlinks, enhanced graph, editor power-ups. See `.ai/obsidian-n
207207

208208
### Phase W: Polish, Ecosystem & Delight
209209

210-
- [ ] **Sprint W-0: Nav Menu Reorg Mode** — Small icon button near Dashboard in sidebar. Click to enter reorg mode for current level (categories or entities). Category level: drag to reorder category icons. Entity level: drag to reorder, create folders/submenus. Click again to exit reorg mode. Must work on desktop, tablet, and mobile. Button is context-aware: on base nav, reorders categories; drilled into a category, reorders entities.
210+
- [~] **Sprint W-0: Nav Menu Reorg Mode** — Small icon button near Dashboard in sidebar. Click to enter reorg mode for current level (categories or entities). Category level: drag to reorder category icons. Entity level: drag to reorder, create folders/submenus. Click again to exit reorg mode. Must work on desktop, tablet, and mobile. Button is context-aware: on base nav, reorders categories; drilled into a category, reorders entities.
211211
- [ ] **Sprint W-0.5: Owner Visual Customization** — Change "Chronicle" brand name per-campaign with optional image/logo. Top bar color/gradient/animation/background image (responsive). Visual customization editor with faux site outline (editable boxes for colors/backgrounds). Appearance-only, not layout editing. Future: per-addon "feature in use" indicators showing which entities/pages use each feature, which widgets are available, click associations to navigate. Disabled feature banner with "offline" sticker instead of complete removal.
212212
- [ ] **Sprint W-1: Command Palette & Saved Filters** — Ctrl+Shift+P action palette with fuzzy search. Saved entity list filter presets as sidebar links in `saved_filters` table.
213213
- [ ] **Sprint W-2: Map Drawing Tools, Regions & Measurement** — Leaflet.Draw integration (freehand, polygons, circles, rectangles, text). Uses existing `map_drawings` table. Per-drawing visibility, color/opacity. Also: map regions (polygon fills/strokes/labels), measurement/distance tool, map embed layout block for entity pages.

internal/templates/layouts/app.templ

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,22 @@ templ Sidebar() {
207207
hx-select="#main-content"
208208
hx-swap="innerHTML show:window:top"
209209
>
210-
<div class="px-4 mb-2 text-xs font-semibold uppercase tracking-wider text-gray-500 sidebar-cat-list-label">
211-
Categories
210+
<div class="px-4 mb-2 flex items-center justify-between sidebar-cat-list-label">
211+
<span class="text-xs font-semibold uppercase tracking-wider text-gray-500">Categories</span>
212+
if GetCampaignRole(ctx) >= 3 {
213+
<button
214+
id="sidebar-reorg-toggle"
215+
type="button"
216+
class="w-5 h-5 flex items-center justify-center rounded text-gray-500 hover:text-gray-300 hover:bg-white/10 transition-colors"
217+
data-campaign-id={ GetCampaignID(ctx) }
218+
title="Reorder sidebar"
219+
>
220+
<i class="fa-solid fa-grip-vertical text-[10px]"></i>
221+
</button>
222+
}
212223
</div>
213224
for _, et := range GetEntityTypes(ctx) {
214-
@entityTypeLink(et.Slug, et.NamePlural, et.Color, et.Icon, GetEntityCount(ctx, et.ID))
225+
@entityTypeLink(et.Slug, et.NamePlural, et.Color, et.Icon, GetEntityCount(ctx, et.ID), et.ID)
215226
}
216227
<a
217228
href={ templ.SafeURL(fmt.Sprintf("/campaigns/%s/entities", GetCampaignID(ctx))) }
@@ -555,7 +566,7 @@ templ DefaultSidebarNav() {
555566

556567
// entityTypeLink renders a single category link in the sidebar icon list.
557568
// Clicking triggers the slide-over panel via sidebar_drill.js.
558-
templ entityTypeLink(slug string, label string, color string, icon string, count int) {
569+
templ entityTypeLink(slug string, label string, color string, icon string, count int, typeID int) {
559570
<a
560571
href={ templ.SafeURL(fmt.Sprintf("/campaigns/%s/%s", GetCampaignID(ctx), slug)) }
561572
hx-boost="false"
@@ -565,6 +576,7 @@ templ entityTypeLink(slug string, label string, color string, icon string, count
565576
templ.KV(sidebarNavInactive, !isPathPrefix(ctx, fmt.Sprintf("/campaigns/%s/%s", GetCampaignID(ctx), slug))) }
566577
data-cat-slug={ slug }
567578
data-cat-url={ fmt.Sprintf("/campaigns/%s/%s", GetCampaignID(ctx), slug) }
579+
data-entity-type-id={ strconv.Itoa(typeID) }
568580
>
569581
<span class="w-4 h-4 mr-3 shrink-0 flex items-center justify-center">
570582
if icon != "" {

internal/templates/layouts/base.templ

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ templ Base(title string) {
6969
<script src="/static/js/sidebar_drill.js" defer></script>
7070
<!-- Sidebar tree: collapsible hierarchy + drag-and-drop reordering -->
7171
<script src="/static/js/sidebar_tree.js" defer></script>
72+
<!-- Sidebar reorg mode: toggle inline category/entity reordering -->
73+
<script src="/static/js/sidebar_reorg.js" defer></script>
7274

7375
<!-- Recently viewed entities tracker (localStorage-backed sidebar list) -->
7476
<script src="/static/js/recent_entities.js" defer></script>

internal/widgets/relations/.ai.md

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ directed links between two entities within a campaign (e.g., "allied with",
88
is created automatically. Provides API endpoints for CRUD operations and a
99
frontend widget mounted on entity profile pages.
1010

11+
Also provides an interactive **graph visualization** (D3.js force-directed)
12+
showing all entities and their connections, with filtering, @mention edges,
13+
orphan detection, and local/ego-graph mode.
14+
1115
## Tier
1216

1317
Widget (Reusable UI block with API endpoints)
@@ -25,27 +29,44 @@ Widget (Reusable UI block with API endpoints)
2529
types.
2630
- **Bi-directional Delete:** Deleting one direction automatically deletes the
2731
reverse to maintain graph consistency.
32+
- **GraphData:** Combined node/edge dataset for graph visualization. Nodes are
33+
entities with position/type data; edges are relations or @mention links.
34+
- **EdgeKind:** Distinguishes "relation" edges (explicit links) from "mention"
35+
edges (inferred from @mention links in entity entry_html).
36+
- **GraphFilter:** Server-side filter struct supporting: entity type slugs,
37+
name search, focus entity + hop depth (BFS), include_mentions, include_orphans.
38+
- **MentionLinkProvider:** Interface bridging the entities plugin to the relations
39+
widget for campaign-wide @mention edge extraction (avoids circular imports).
40+
- **EntityTypeListerForGraph:** Interface for fetching entity type summaries
41+
used in the graph page filter dropdown.
42+
- **Orphan Entities:** Entities with zero connections (no relations, no mentions).
43+
Detected by subtracting graph-connected entities from all campaign entities.
44+
- **Local/Ego Graph:** BFS subgraph extraction from a focus entity to N hops
45+
depth. Used by the `local_graph` layout block on entity profile pages.
2846

2947
## Files
3048

3149
| File | Purpose |
3250
|------|---------|
33-
| model.go | Relation struct, request DTOs, common relation types |
34-
| repository.go | SQL queries for entity_relations table |
35-
| service.go | Validation, bi-directional create/delete logic |
36-
| handler.go | JSON API handlers for relation CRUD |
51+
| model.go | Relation struct, request DTOs, common relation types, GraphNode, GraphEdge, GraphData, GraphFilter, MentionLinkProvider, EntityTypeListerForGraph |
52+
| repository.go | SQL queries for entity_relations table, ListOrphanEntities |
53+
| service.go | Validation, bi-directional create/delete, GetGraphData, GetFilteredGraphData, bfsSubgraph, mention edge merging |
54+
| handler.go | JSON API handlers for relation CRUD, GraphAPI (filtered graph data), GraphPage (full-page graph view) |
3755
| routes.go | Campaign-scoped routes with role-based access |
56+
| graph.templ | Graph page template with filter toolbar and entity type data attributes |
3857

3958
## Frontend
4059

4160
| File | Purpose |
4261
|------|---------|
43-
| static/js/widgets/relations.js | Relations display/management widget |
62+
| static/js/widgets/relations.js | Relations display/management widget on entity pages |
63+
| static/js/widgets/relation_graph.js | D3.js force-directed graph with filtering, mention edges, clustering, orphan display, local graph mode |
4464

4565
## Dependencies
4666

4767
- **Uses:** campaigns (CampaignContext, RequireRole middleware), auth (RequireAuth, GetUserID), apperror
48-
- **Used by:** Entity profile pages display relations via the frontend widget
68+
- **Receives via injection:** MentionLinkProvider (from entities plugin, wired in routes.go), EntityTypeListerForGraph (from entities plugin)
69+
- **Used by:** Entity profile pages (relations widget), entity layout blocks (local_graph block), standalone graph page
4970

5071
## Routes
5172

@@ -55,6 +76,8 @@ Widget (Reusable UI block with API endpoints)
5576
| POST | /campaigns/:id/entities/:eid/relations | CreateRelation | Create bi-directional relation (Scribe+) |
5677
| DELETE | /campaigns/:id/entities/:eid/relations/:rid | DeleteRelation | Delete relation and reverse (Scribe+) |
5778
| GET | /campaigns/:id/relation-types | GetCommonTypes | Predefined relation type pairs |
79+
| GET | /campaigns/:id/relations-graph/data | GraphAPI | Filtered graph data (JSON) with query params: types, search, focus, hops, include_mentions, include_orphans |
80+
| GET | /campaigns/:id/relations-graph/page | GraphPage | Full-page graph visualization with filter toolbar |
5881

5982
## Database
6083

@@ -85,4 +108,11 @@ Unique: (source_entity_id, target_entity_id, relation_type)
85108
- [x] Frontend relations widget (JS)
86109
- [x] Relations block on entity profile pages
87110
- [x] Relations block in template editor palette
111+
- [x] Graph visualization page (D3.js force-directed)
112+
- [x] Graph API with filtering (types, search, focus/hops, mentions, orphans)
113+
- [x] @Mention edge extraction and display
114+
- [x] Local/ego graph mode (BFS subgraph)
115+
- [x] Graph filter toolbar UI
116+
- [x] Entity type clustering in graph
117+
- [x] Orphan entity detection and display
88118
- [ ] Tests written

static/css/input.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,33 @@
817817
border-radius: 0 4px 4px 0;
818818
}
819819

820+
/* Reorg mode: drop target highlight for category links. */
821+
.sidebar-reorg-drop-target {
822+
background-color: rgba(99, 102, 241, 0.12) !important;
823+
box-shadow: inset 0 0 0 1px rgba(99, 102, 241, 0.4);
824+
border-radius: 4px;
825+
}
826+
827+
/* Reorg mode: drag handle visibility (hidden by default, shown in reorg). */
828+
.reorg-drag-handle {
829+
opacity: 0.5;
830+
transition: opacity 150ms ease;
831+
}
832+
.reorg-drag-handle:hover {
833+
opacity: 1;
834+
}
835+
836+
/* Reorg mode: subtle border on reorderable items. */
837+
.sidebar-reorg-active #sidebar-cat-list .sidebar-category-link,
838+
.sidebar-reorg-active #sidebar-entity-tree .sidebar-tree-node {
839+
transition: background-color 150ms ease, box-shadow 150ms ease;
840+
}
841+
842+
/* Reorg mode active indicator on toggle button. */
843+
#sidebar-reorg-toggle.bg-accent\/20 {
844+
background-color: rgba(var(--color-accent-rgb, 99, 102, 241), 0.2);
845+
}
846+
820847
/* Tree toggle button styling. */
821848
.sidebar-tree-toggle {
822849
transition: transform 150ms ease;

0 commit comments

Comments
 (0)