# 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*
