docs(38): capture phase context
This commit is contained in:
132
.planning/phases/38-admin-tag-management/38-CONTEXT.md
Normal file
132
.planning/phases/38-admin-tag-management/38-CONTEXT.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Phase 38: Admin — Tag Management - Context
|
||||||
|
|
||||||
|
**Gathered:** 2026-04-19
|
||||||
|
**Status:** Ready for planning
|
||||||
|
|
||||||
|
<domain>
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
Build the `/admin/tags` list and `/admin/tags/$tagId` edit page, wired into the Phase 36 admin shell. Admins can browse all tags in a collapsible tree view, create new tags via a quick-add form, rename/reparent tags on a dedicated edit page, and delete tags with an impact-aware confirmation. Adds `parentId` to the tags table to enable arbitrary-depth parent-child hierarchy.
|
||||||
|
|
||||||
|
No new capabilities beyond create/rename/reparent/delete and hierarchy visualization.
|
||||||
|
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
<decisions>
|
||||||
|
## Implementation Decisions
|
||||||
|
|
||||||
|
### Schema Migration
|
||||||
|
- **D-01:** Add `parentId integer REFERENCES tags(id) ON DELETE SET NULL NULLABLE` to the `tags` table via Drizzle migration. Self-referential FK — no depth limit at the schema level.
|
||||||
|
- **D-02:** On parent deletion, `ON DELETE SET NULL` orphans children (they become top-level). No cascading child deletion.
|
||||||
|
|
||||||
|
### List View — Collapsible Tree
|
||||||
|
- **D-03:** Collapsible tree view (not a flat table). Parent tags render rows; children are indented below. All nodes start expanded on page load.
|
||||||
|
- **D-04:** Columns: Name (with indent level visual) | Item Count | Actions.
|
||||||
|
- **D-05:** Search/filter mode: filter in-tree — non-matching rows are hidden in place. Parents with matching children remain visible; non-matching leaf nodes are hidden.
|
||||||
|
- **D-06:** No separate "Children Count" column — children are visible in the tree structure below each parent.
|
||||||
|
|
||||||
|
### Create UX
|
||||||
|
- **D-07:** Quick-add form at the top of the tag list (not a separate page). Fields: name + optional parent picker. Submits inline without navigation.
|
||||||
|
|
||||||
|
### Edit UX
|
||||||
|
- **D-08:** Dedicated edit page at `/admin/tags/$tagId` — consistent with Phase 37 item edit pattern. Fields: name + parent picker.
|
||||||
|
- **D-09:** Delete lives on the edit page (not the list), following Phase 37 D-05.
|
||||||
|
|
||||||
|
### Hierarchy Depth
|
||||||
|
- **D-10:** Arbitrary depth — any tag can be a parent of any other (no 1-level limit). Future-proofed for deep taxonomies.
|
||||||
|
- **D-11:** Cycle prevention is dual-layer:
|
||||||
|
- **Client**: Parent picker filters out the tag's own descendants from the dropdown options.
|
||||||
|
- **Server**: API validates that the new parent is not a descendant of the tag being updated. Returns 400 with a clear error message if a cycle is detected.
|
||||||
|
|
||||||
|
### Delete Behavior
|
||||||
|
- **D-12:** Deleting a tag orphans its children — they become top-level tags (`parentId` SET NULL via FK cascade).
|
||||||
|
- **D-13:** Delete confirmation dialog shows: item count + child count warning. Example: "Delete 'sleeping-bag'? 12 items use this tag. Its 3 child tags will become top-level. This cannot be undone."
|
||||||
|
- **D-14:** If a tag has 0 items and 0 children, the confirmation is simplified: "Delete 'sleeping-bag'? This cannot be undone."
|
||||||
|
|
||||||
|
### Claude's Discretion
|
||||||
|
- Exact visual styling of tree indentation (border-left line, chevron icon, or indent padding)
|
||||||
|
- Whether to use a chevron toggle button or click-to-collapse on the row
|
||||||
|
- Implementation of the collapsible tree (local component state vs. Zustand)
|
||||||
|
- Exact error message copy for cycle detection rejection
|
||||||
|
- Whether to add a "Move to top-level" shortcut on the edit page in addition to the parent picker
|
||||||
|
|
||||||
|
</decisions>
|
||||||
|
|
||||||
|
<canonical_refs>
|
||||||
|
## Canonical References
|
||||||
|
|
||||||
|
**Downstream agents MUST read these before planning or implementing.**
|
||||||
|
|
||||||
|
### Existing Tag Infrastructure
|
||||||
|
- `src/server/services/tag.service.ts` — `getAllTags`; extend with CRUD functions (createTag, updateTag, deleteTag, getTagWithCounts)
|
||||||
|
- `src/server/routes/tags.ts` — existing `GET /api/tags`; admin CRUD routes go under `/api/admin/tags`
|
||||||
|
- `src/db/schema.ts` — `tags` table (currently `id`, `name`, `createdAt`); add `parentId` here
|
||||||
|
|
||||||
|
### Admin Infrastructure (Phase 36/37)
|
||||||
|
- `src/server/routes/admin-items.ts` — admin item routes pattern; follow same structure for admin tag routes
|
||||||
|
- `src/server/middleware/auth.ts` — `requireAdmin` middleware
|
||||||
|
- `src/client/routes/admin.tsx` — admin shell layout with `<Outlet />`; "Tags" nav item needs to be enabled (remove cursor-not-allowed, add active Link)
|
||||||
|
- `src/client/routes/admin/index.tsx` — admin index placeholder (unchanged)
|
||||||
|
- `src/client/routes/admin/items.tsx` — list page pattern to follow
|
||||||
|
- `src/client/routes/admin/items.$itemId.tsx` — edit page pattern to follow
|
||||||
|
|
||||||
|
### Client Hooks
|
||||||
|
- `src/client/hooks/useTags.ts` — existing hook; extend or create `useAdminTags` following `useAdminGlobalItems` pattern
|
||||||
|
- `src/client/hooks/useAdminGlobalItems.ts` — pattern to follow for admin tag hooks
|
||||||
|
- `src/client/lib/api.ts` — `apiGet`, `apiPost`, `apiPut`, `apiDelete` fetch wrappers
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- `.planning/REQUIREMENTS.md` — ADMN-05, ADMN-06, ADMN-07, ADMN-08, ADMN-09, ADMN-10
|
||||||
|
|
||||||
|
</canonical_refs>
|
||||||
|
|
||||||
|
<code_context>
|
||||||
|
## Existing Code Insights
|
||||||
|
|
||||||
|
### Reusable Assets
|
||||||
|
- `getAllTags` service function: returns `id` + `name` — extend to include `parentId`, `itemCount`, `childCount`
|
||||||
|
- `apiGet`, `apiPost`, `apiPut`, `apiDelete` from `src/client/lib/api.ts` — same fetch wrappers as Phase 37
|
||||||
|
- Admin shell sidebar: "Tags" entry already exists (Phase 36) but is disabled — just enable the Link
|
||||||
|
|
||||||
|
### Established Patterns
|
||||||
|
- Admin list page: See `src/client/routes/admin/items.tsx` for the search + table + infinite scroll pattern
|
||||||
|
- Admin edit page: See `src/client/routes/admin/items.$itemId.tsx` for the form + delete-at-bottom pattern
|
||||||
|
- React Query hooks: `useAdminGlobalItems` is the pattern for admin data hooks
|
||||||
|
- Delete confirmation: Destructive modal pattern already used in Phase 37 item delete
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- `src/client/routes/admin.tsx`: Change "Tags" sidebar entry from disabled `<div>` to `<Link to="/admin/tags">`
|
||||||
|
- `src/server/routes/admin.ts` (or `src/server/index.ts`): Register admin tag routes under `/api/admin/tags`
|
||||||
|
- `src/db/schema.ts` + Drizzle migration: Add `parentId` column to `tags` table
|
||||||
|
|
||||||
|
### Key Absence
|
||||||
|
- No tree component exists in the codebase — this must be built from scratch. Keep it simple: Tailwind indentation + local expand/collapse state.
|
||||||
|
|
||||||
|
</code_context>
|
||||||
|
|
||||||
|
<specifics>
|
||||||
|
## Specific Ideas
|
||||||
|
|
||||||
|
- Tree row indent: 16-24px per level, visual hierarchy via a subtle left border or padding (consistent with app's minimal aesthetic)
|
||||||
|
- Chevron/triangle icon (▶/▼) on parent rows to toggle expand/collapse
|
||||||
|
- Quick-add form at top of list: name input + parent picker dropdown + "Add" button
|
||||||
|
- Edit page back link: "← Tags" to return to list (same as Phase 37's "← Items")
|
||||||
|
- Delete button at bottom of edit form, styled destructive red
|
||||||
|
- Parent picker on edit page: searchable dropdown that excludes the tag itself and all its descendants
|
||||||
|
|
||||||
|
</specifics>
|
||||||
|
|
||||||
|
<deferred>
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Drag-to-reparent: drag a tag row onto a parent to reassign hierarchy — complex UX, out of scope for Phase 38
|
||||||
|
- Bulk operations (bulk delete, bulk reparent) — single-item workflow only in this phase
|
||||||
|
- Tag merge (merge two tags into one, reassigning all items) — separate capability, future milestone
|
||||||
|
- Tag usage analytics (which tags are most used, fastest growing) — deferred to v2.5 engagement stats
|
||||||
|
|
||||||
|
</deferred>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 38-admin-tag-management*
|
||||||
|
*Context gathered: 2026-04-19*
|
||||||
149
.planning/phases/38-admin-tag-management/38-DISCUSSION-LOG.md
Normal file
149
.planning/phases/38-admin-tag-management/38-DISCUSSION-LOG.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Phase 38: Admin — Tag Management - Discussion Log
|
||||||
|
|
||||||
|
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||||
|
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||||
|
|
||||||
|
**Date:** 2026-04-19
|
||||||
|
**Phase:** 38-admin-tag-management
|
||||||
|
**Areas discussed:** Create/Edit UX, Hierarchy display in list, Delete scope for parent tags, Hierarchy depth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Create / Edit UX
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Inline modal for both | Single modal handles create AND edit. No separate route. | |
|
||||||
|
| Dedicated edit page (like Phase 37) | Consistent with items pattern at /admin/tags/$tagId. | ✓ |
|
||||||
|
| Inline table editing | Click-to-edit in table rows. | |
|
||||||
|
|
||||||
|
**User's choice:** Dedicated edit page for editing (consistent with Phase 37 pattern)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Create UX (follow-up)
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Quick-add row / form at top of list | Name input + parent picker inline at list top. | ✓ |
|
||||||
|
| New tag via /admin/tags/new page | Dedicated page for creation. | |
|
||||||
|
|
||||||
|
**User's choice:** Quick-add form at the top of the tag list
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hierarchy display in list
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Flat table with Parent column | Each row shows parent name, consistent with Phase 37. | |
|
||||||
|
| Indented tree view | Children indented below parents, hierarchical visualization. | ✓ |
|
||||||
|
|
||||||
|
**User's choice:** Indented tree view
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tree + Filter behavior
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Flatten on search | Switch to flat list when searching. | |
|
||||||
|
| Filter in-tree | Hide non-matching rows in place, keep tree structure. | ✓ |
|
||||||
|
|
||||||
|
**User's choice:** Filter in-tree (non-matching rows hidden, matching stay in place)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tree columns
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Name + Item Count + Actions | Clean, minimal. | ✓ |
|
||||||
|
| Name + Item Count + Children Count + Actions | Explicit child count. | |
|
||||||
|
|
||||||
|
**User's choice:** Name + Item Count + Actions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Delete scope for parent tags
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Orphan children (become top-level) | Children lose parent, become top-level. | ✓ |
|
||||||
|
| Block deletion until reparented | Admin must reparent children first. | (initially selected, then pivoted) |
|
||||||
|
| Cascade delete children | Children deleted with parent. | |
|
||||||
|
|
||||||
|
**User's choice:** Initially selected "Block deletion until reparented", then pivoted to "Orphan children (become top-level)" — user said "the moving up ain't too bad, i think we should pivot to that"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Delete confirmation dialog
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Show item count + child count warning | "12 items use this tag. Its 3 child tags will become top-level." | ✓ |
|
||||||
|
| Show item count only | Children silently become top-level with no warning. | |
|
||||||
|
|
||||||
|
**User's choice:** Show item count + child count warning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hierarchy depth
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Strictly 1 level deep | Parent/child only. Simpler schema and rendering. | |
|
||||||
|
| Arbitrary depth | Any tag can be a parent. Cycle detection needed. | ✓ |
|
||||||
|
|
||||||
|
**User's choice:** Arbitrary depth — user said "if we don't do this right now we could hurt ourselves at some point, build it so it can handle however deep we want"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cycle prevention
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Server-side validation only | API rejects cycles. | |
|
||||||
|
| Filter parent picker in UI too | Dropdown excludes descendants. | |
|
||||||
|
| Both | UI filters + server validates. | ✓ |
|
||||||
|
|
||||||
|
**User's choice:** Both — "to prevent api mishaps"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tree expand/collapse
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| All expanded, no collapse | Simple. | |
|
||||||
|
| Collapsible nodes | Parents can be expanded/collapsed. | ✓ |
|
||||||
|
|
||||||
|
**User's choice:** Collapsible nodes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Initial expand state
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| All expanded | Everything visible on load. | ✓ |
|
||||||
|
| Top-level only (children collapsed) | Admin expands on demand. | |
|
||||||
|
|
||||||
|
**User's choice:** All expanded on page load
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Claude's Discretion
|
||||||
|
|
||||||
|
- Exact visual styling of tree indentation
|
||||||
|
- Whether chevron or click-on-row to collapse
|
||||||
|
- Collapsible tree implementation (local state vs. Zustand)
|
||||||
|
- Exact error message copy for cycle detection
|
||||||
|
- "Move to top-level" shortcut on edit page
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Drag-to-reparent — complex UX, out of scope
|
||||||
|
- Bulk operations — single-item workflow for Phase 38
|
||||||
|
- Tag merge — future milestone
|
||||||
|
- Tag analytics — v2.5
|
||||||
Reference in New Issue
Block a user