mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
* feat(labels): add issue label CRUD + attach/detach handlers (#1191) The issue_label and issue_to_label tables were scaffolded in 001_init.up.sql but never wired to any code path. This commit ships the backend for #1191: - Migration 048: adds created_at/updated_at timestamps + workspace-scoped case-insensitive unique index on label names - sqlc queries for label CRUD + issue<->label attach/detach + batch list (ListLabelsByIssueIDs for board/list views) - HTTP handlers: /api/labels CRUD, /api/issues/{id}/labels attach/detach - Protocol events: label:{created,updated,deleted} + issue_labels:changed - Handler tests covering CRUD, duplicate-name conflict, invalid-color, attach/detach idempotency, and cross-workspace isolation * feat(cli): add label and issue label subcommands (#1191) - multica label {list,get,create,update,delete} - multica issue label {list,add,remove} Both follow existing CLI conventions (JSON/table output, flag shapes) and exercise the /api/labels endpoints shipped in the previous commit. * feat(web): add labels UI — picker with inline create + management dialog (#1191) Exposes the backend label feature to users via the existing issue-detail sidebar. - `@multica/core/types/label` — Label, CreateLabelRequest, UpdateLabelRequest, plus response envelopes - `@multica/core/api/client` — 8 methods for label CRUD and issue↔label attach/detach - `@multica/core/labels` — labelKeys, queryOptions, and mutation hooks with optimistic updates (matches the project/ module layout) - WS event type literals extended for label:{created,updated,deleted} and issue_labels:changed - `views/labels/label-chip.tsx` — colored pill; uses relative luminance (ITU-R BT.601) to pick #111827 or #f9fafb text so chips stay readable on both pastel and saturated backgrounds - `views/issues/components/pickers/label-picker.tsx` - Multi-select combobox in the issue sidebar - When 0 labels: "Add label" trigger - When 1+ labels: the chips themselves are the trigger; × on each chip detaches without opening the picker - Inline create: typing a new name + Enter creates with a hash-derived color and attaches in one motion (matches Linear/GitHub) - "Manage labels…" footer opens a dialog containing the full workspace panel — users never leave the issue context to rename/recolor/delete - `views/issues/components/labels-panel.tsx` — workspace labels manager. Single-row create form (color swatch + name + Add button). Each label row supports inline rename + recolor + delete (with confirm dialog). Color input uses the browser's native picker for full-gamut access — no preset palette clutter. - `PropRow label="Labels"` added to the issue-detail sidebar below Project Labels are issue metadata everyone uses — not admin configuration. Putting them in Settings next to destructive workspace actions misframed them; adding a top-level nav entry or a sibling tab to the Issues page added surface area that wasn't earning its keep for a feature users touch occasionally. Keeping management in a dialog launched from the picker itself keeps users in their issue context and matches how GitHub handles label editing from the label selector.
30 lines
1.5 KiB
SQL
30 lines
1.5 KiB
SQL
-- Add timestamp columns to issue_label so labels track their own lifecycle.
|
|
-- The table was scaffolded in 001_init.up.sql but never wired up to any code
|
|
-- path; timestamps are added here as a precondition for the new CRUD handlers,
|
|
-- CLI, and UI (see #1191).
|
|
|
|
ALTER TABLE issue_label
|
|
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT now();
|
|
|
|
-- Dedupe case-insensitive collisions before the unique index is created.
|
|
-- Self-hosted deployments may have `Bug` + `bug` pairs from manual poking at
|
|
-- the table, which would otherwise abort this migration when the unique index
|
|
-- is added below. Keep the oldest row intact (smallest id, by lexicographic
|
|
-- UUID order — effectively earliest-inserted since ids are gen_random_uuid()),
|
|
-- rename every later duplicate by appending a short UUID suffix. Visible only
|
|
-- on the rare installs that had duplicates; a no-op otherwise.
|
|
UPDATE issue_label AS il
|
|
SET name = il.name || ' (' || substring(il.id::text, 1, 8) || ')'
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM issue_label il2
|
|
WHERE il2.workspace_id = il.workspace_id
|
|
AND LOWER(il2.name) = LOWER(il.name)
|
|
AND il2.id < il.id
|
|
);
|
|
|
|
-- Workspace-scoped uniqueness on label name. Case-insensitive to avoid
|
|
-- "Bug" / "bug" drift that would confuse users in the picker UI.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS issue_label_workspace_name_lower_idx
|
|
ON issue_label (workspace_id, LOWER(name));
|