mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
admin-workspace-create2
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5336391978 |
MUL-7299: feat(issues): filter issues by the parent project's status (#8321)
Adds "Project status" as its own issue filter dimension, next to "Project": pick "In Progress" once instead of ticking every active project, and the result follows project lifecycle changes instead of freezing a project-id snapshot. Server side extends the existing issue table filter protocol with an optional, validated `project_statuses`, filtered through a parameterized, workspace-bound EXISTS on `project`. No migration and no new index — the predicate resolves through `project_pkey`, and the planner falls back to the existing `idx_issue_project_status`. Client side wires it as a normal dimension: filter menu, chip, reset, saved views and Gantt/Swimlane, with the project catalog treated as "cannot answer yet" rather than "no filter" while it loads. Project status changes, deletes and realtime project events invalidate the issue table cache, since issue rows themselves do not change. Default off; closing the filter reverts the behavior. |
||
|
|
9d18186e6e |
fix(status): MUL-7365 expand category compatibility before backfill (#8407)
* fix(status): stage category expansion and preserve installed client groups (MUL-7365) Co-authored-by: multica-agent <[email protected]> * fix(status): validate mixed storage with GC and recovery compatibility Co-authored-by: multica-agent <[email protected]> * docs(status): align phase one rollout with merged main dependency Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
4fe6586b32 |
MUL-6790 feat(issues): support operator matching in scalar property filters (#7765)
* feat(issues): support operator matching in scalar property filters Closes #7692. Scalar custom-property filters (text / number / date / url) previously matched by exact equality only, so a text fragment or a numeric range returned an empty list. A property filter member is now either a plain string (exact equality — the unchanged default that keeps every existing saved view, URL, and API call meaningful) or an operator object: - contains — case-insensitive substring (text / url) - gt / gte / lt / lte — numeric comparison (number) - before / after — lexicographic comparison of the stored "YYYY-MM-DD" strings (date) Backend: parsePropertiesFilterParam accepts {op, value} members and compiles them to operator patterns; propertiesFilterPredicate renders ILIKE / numeric / date-string comparisons and the static ListOpenIssues unroll in issue.sql mirrors them. contains values are LIKE-escaped at parse time and ops are fully validated before compilation, so the SQL branches only ever see legal shapes. The table-filters request carries members as raw JSON so both shapes survive the round-trip, and the query fingerprint canonicalizes them byte-exactly. Frontend: the filter menu grows an operator row above the scalar value input ("is" is the default and still commits a bare string), the client matcher in issueMatchesPropertyFilters mirrors the server predicates, and chips summarize operator members with per-locale phrases or math symbols. Operator members ride in saved-view baselines and chip deltas through a canonical propertyFilterValueKey. Verified: full Go suite (race) and the internal/handler property, facet, and issue-table tests; new unit + DB-backed operator tests on both tiers; pnpm typecheck and the core/views Vitest suites including new operator matrix, menu, and saved-view baseline tests. * test(issues): cover operator members through the table-rows endpoint The properties filter serves through three paths — the paginated list, the open_only static unroll, and the table rows/facets compile — but only the first two had operator-member coverage. The table path has PR-specific code (raw-JSON members in issueTableFiltersRequest and the byte-exact sortedUniqueRawJSON fingerprint) that no test exercised. Drive ListIssueTableRows with operator members ANDed across two definitions so the decode → fingerprint → parsePropertiesFilterParam chain is pinned like the other paths. * fix(issues): canonicalize numeric operator bounds and align empty-needle contains Adversarial review findings: - strconv.ParseFloat accepts forms Postgres ::numeric rejects ("0x1p4" hex-float, "1_000" underscores on older PG). The dynamic predicate binds the parsed float and succeeds while the static open_only unroll casts the raw string inside SQL and 500s — same filter, two list endpoints, different outcomes. Canonicalize the compiled pattern value with strconv.FormatFloat(num, 'f', -1, 64) at validation time; the dynamic re-parse and the static cast now see plain decimals. Regression tests cover the hex-float bound on both serving paths. - issueValueMatchesOperator treated an empty contains needle as match-all ("".includes("") is true) while the server rejects empty operator values — a hand-edited saved-view blob could match every issue client-side. The matcher now refuses empty needles, and the test asserts it against a SET value (the unset case short-circuits before the operator runs and could never catch this). - Server e2e: pin unset-key exclusion for contains / before / after on both serving paths via the seeded empty issue. * fix(issues): align scalar property operator semantics * test(issues): cover boolean contains mismatch * fix(i18n): use exclusive Japanese wording for date before/after filters The `before` / `after` operators compare strictly (`<` / `>` on both the server predicate and the client matcher), so a date equal to the bound is never a match. Japanese 以前 / 以後 mean "on or before" / "on or after" — they include the reference date, so a user filtering 「2026-03-01 以前」 reasonably expects March 1 in the results and does not get it. より前 / より後 carry the exclusive meaning the implementation actually has. English "before"/"after" and Chinese 早于/晚于 are already exclusive and are left unchanged. Korean 이전 / 이후 share the same Sino root and are inclusive by dictionary definition, but they are also the conventional rendering for before/after filters in Korean software — left as-is pending a native speaker's call rather than guessed at. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
5fa65bd125 |
feat(billing): enforce Cloud issue count limits (#7619)
* feat(billing): enforce Cloud issue count limits * fix(billing): address issue limit review feedback * test(billing): exercise validation as manager * fix(billing): resolve second review feedback * fix(core): preserve entitlement limit discrimination |
||
|
|
1dc135598e |
MUL-6343: Enforce recently created issue windows (#7305)
* MUL-6343: enforce recently created issue windows Co-authored-by: multica-agent <[email protected]> * fix(issues): address creation window review Co-authored-by: multica-agent <[email protected]> * fix(issues): bound table window ID cache Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e3a40b0b8d |
feat(MUL-6243): custom issue status UI — picker, board, filters, settings (#7103)
* refactor(views): widen IssueStatus to open key, resolve category at render sites Board columns, swimlane columns and hidden-column panels are CATEGORIES, not status keys, so adding a custom status never adds a column. Retyped those to IssueStatusCategory and made every presentation lookup (STATUS_CONFIG, i18n labels, status icons) resolve the key to its category first. Status filters stay in concrete keys and map back to columns via the catalog. MUL-6243 Co-authored-by: multica-agent <[email protected]> * feat(views): catalog-driven status picker and per-status labels The picker now offers the workspace catalog's active statuses grouped by category, instead of the 7 hardcoded built-ins, and falls back to the built-ins until the catalog lands so a cold render is unchanged. Built-in names always resolve through i18n, never through the catalog's server-seeded English name — otherwise every non-English workspace would see 'In Progress' where it used to see '进行中'. Custom names come from the catalog. That rule lives in one place: useStatusLabel. Applied it to every surface that shows an issue's OWN status (detail activity feed, table group headers and CSV export, hover card, inbox), which previously showed the category label and so could not tell two statuses in the same category apart. MUL-6243 Co-authored-by: multica-agent <[email protected]> * feat(views): show custom statuses on cards and in filters Board columns and list sections are categories, so two issues in the same column can sit on different statuses with nothing to tell them apart. CustomStatusChip is that signal, and it renders nothing for a built-in — a workspace with no custom statuses looks unchanged. The status filter now lists the workspace catalog instead of the 7 hardcoded built-ins. Without this an issue moved onto a custom status could not be narrowed to at all. Picker and filter share one useStatusOptions hook so the two lists cannot drift apart. MUL-6243 Co-authored-by: multica-agent <[email protected]> * feat(settings): issue status catalog management with drag reorder Owner/admin CRUD for custom statuses, organised by CATEGORY rather than as one flat list. Category is not decoration here — it is the behavior a status inherits, and picking it is the one moment an admin can, since it is immutable afterwards. Built-ins are shown but locked: each is its category's canonical definition. Drag reorder writes intra-category positions starting at 1, because the built-in is seeded at 0 and PATCH on it is a 403, so it always heads its category. The optimistic cache write re-SORTS rather than only writing positions, or the drag would visually undo itself until the refetch. custom_issue_statuses is now published to the frontend, so the tab can hide creation when the deployment has the rollout gate off instead of offering a button that 403s. MUL-6243 Co-authored-by: multica-agent <[email protected]> * fix(issues): close the category contract end to end Elon's review found the board only half-converted: the UI targeted categories while the server still paged by concrete status key, so a card on a custom status was dropped twice — it never came back from the server, and the row filter would have discarded it if it had. 1. New `status_category` group kind (and compound secondary axis). Grouping rewrites the column with a static CASE built from the catalog; paging expands the category to concrete keys and stays on `status = ANY(...)`, so (workspace_id, status) is still usable. A workspace with no custom statuses gets exactly `i.status` back. Row filter, facet totals and swimlane cells all fold by category now. 2. Column visibility split from the status filter. Hiding a column used to write the other six built-in keys into statusFilters, so hiding Backlog silently dropped every CUSTOM status in other categories. Display state is now its own field. The saved-view baseline also stopped deleting custom status filters on reopen. 3. The board/list right-click menu reads the shared catalog options, so every entry point that can set a status offers the same set. 4. Remaining presentation consumers resolve a category instead of guessing: gantt bars were keyed by IssueStatus and went colorless on any custom key; hover card, sidebar, search, tab strip, inbox row, create toast and the activity feed all resolve explicitly now. 5. Reorder is one atomic statement behind a new endpoint, and the client submits ACTIVE rows only. The per-row PATCH loop half-committed when an archived row rejected mid-sequence. Also: one catalog observer per card instead of three, and the stale tasks/todo.md draft is gone. MUL-6243 Co-authored-by: multica-agent <[email protected]> * fix(issues): atomic reorder, category behavior sweep, rollout-safe grouping Second review round. 1. Reorder is now one transaction holding the catalog's SHARED lock. The old shape validated ids, THEN ran the UPDATE — an archive committing in that window left the UPDATE silently skipping the archived row while the earlier rows were rewritten, and still returned 200. The authoritative active set is read under the lock and the payload must match it EXACTLY: a partial order would assign positions from the array index and collide with the rows it left out. Affected rows are checked against the payload before commit. 2. Swept the Web behavior consumers that still compared raw keys, so a custom category actually inherits behavior: gantt hide-completed, batch backlog parking, done/cancelled strikethrough, the mark-done button, sub-issue progress, search cancelled-demotion and mention demotion/dimming. issueBehavesAs is the one predicate they share, and it fails toward showing more and asking more. Two glyph sites (gantt row, swimlane lane) were still painting custom in_review as Todo. 3. Rollout safety. status_category is a contract this PR introduces, and the default-off creation flag does not guard the request path, so a new Web build would have 400'd against any un-upgraded pod — for every workspace. The client now sends it only once the catalog confirms the workspace HAS a custom status, which can only be true if the fleet already serves this version. Everyone else keeps the exact request they made before. That also uncovered a real defect: plain status grouping 500'd on a workspace holding custom statuses, because the descriptor rejected any non-built-in value and failed the whole response. 4. One catalog read per resolve instead of three, and the common path reads none at all. Pinned with observable scan-count tests rather than an assertion that the SQL string matches. 5. A custom-key filter no longer routes on a guess. categoryOf answers todo for anything unloaded, so a saved qa filter paged the todo column while the query still restricted status=qa — empty on every cold load, permanent if the catalog fetch failed. Unresolved custom keys now contribute no column until the catalog is authoritative; facet counts drop unknown keys rather than inflating todo. MUL-6243 Co-authored-by: multica-agent <[email protected]> * fix(issues): swimlane category axis, catalog tri-state, tests with teeth Third review round. 1. Swimlane was only half-converted. The compound spec sent secondary=status_category unconditionally — the same rolling-deploy hole the board/list branches had — and the client row matcher then compared the raw key, so a qa row the server correctly folded into the in_review cell was thrown away again. Both now follow the same hasCustomStatuses switch, and the matcher knows which axis it is on. 2. The cold-load fix did not actually make the surface wait. Returning an empty column set is indistinguishable from "narrow to nothing", so the surface fetched nothing and rendered an empty board with no spinner; on catalog failure that was permanent, with no retry. Resolution is now pending / resolved / error: pending and error suspend fetching AND hold loading, error surfaces isStatusCatalogError. Fetching is suspended rather than left to fall back to all columns, which would briefly show the UNFILTERED board to someone opening a saved qa view. The group contract also joins the branch cursor identity, so a status cursor cannot ride into a status_category request. 3. Two tests were false-green and are replaced: - The reorder race archived BEFORE calling reorder, so the active-set check caught it even with the lock deleted. It now reproduces the real interleaving (reorder parked on the lock, archive commits, then reorder proceeds) and fails without the shared lock — with a 200, which is the actual hole. - The catalog read count read pg_stat_user_tables, whose counters flush asynchronously, and asserted delta <= 1 — vacuous at zero. It now counts through an injected querier and asserts exactly 1 read and 0 category-expansion reads. Also: plain status grouping 500'd on a workspace holding custom statuses (the descriptor rejected non-built-in values and failed the whole response) — that is the path the Table view and every client fallback use. And the chat mention items dropped status_category, so a custom done status in Current/Recent rendered as an active Todo. MUL-6243 Co-authored-by: multica-agent <[email protected]> * fix(issues): give the status catalog failure a real user path Fourth review round. One group, both halves of it. A failed status catalog suspends row fetching, because a custom status filter cannot be routed to a column without it. That left isLoading and isEmpty both false, so IssueSurface fell straight through to the content branch and rendered an empty view with no explanation and no way out — the controller's isStatusCatalogError had no consumer. The catalog now carries its own `retry` (React Query's refetch) rather than just a failure flag, the controller re-exposes it, and IssueSurface renders an explicit error state ahead of loading/empty/content. Component test covers the full loop: catalog rejects, error appears with no row requests issued, clicking retry succeeds, fetching resumes. The blocking condition is also narrowed. A BACKGROUND refetch can fail while the last successful catalog is still cached, and error-before-loaded meant that perfectly serviceable snapshot got a retry screen in front of it. Only a failure with no data behind it blocks now; otherwise the last successful catalog keeps resolving. That rule lives in status-behavior.test.ts, where it can be asserted directly. The component test asserts the wiring — it was originally written to claim the rule too, but reverting the narrowing did not fail it, so the claim moved to the layer that actually holds it. MUL-6243 Co-authored-by: multica-agent <[email protected]> * fix(issues): accept custom status keys in table filters.statuses Filtering a board, list or table by a custom status 400'd the whole request: `filters.statuses` carries the exact keys the user picked, and the table query still validated that list against the 7 built-ins. Every surface errored out instead of narrowing — the one thing the filter work in this PR exists to do. The predicate is a parameterized `status = ANY(...)`, so an unknown key simply matches nothing; the check is now non-empty and length-bounded. Found while writing the post-deploy verification steps, which is where the gap became obvious: "filter by QA" was step one and it 400s. MUL-6243 Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
984a2c2bff |
MUL-5954: feat: saved issue views V1 (MUL-4796) (#6516)
* feat(issues): server-backed saved issue views API (MUL-4796) issue_view table (migrations 262-264: table + two CONCURRENTLY partial indexes) stores named filter definitions: query jsonb is the shared identity, display jsonb only seeds a user's first open. Scope model is workspace / my (with scope_variant, forced private) / project (validated scope_id, deleted with the project in the same app transaction). issue_view_preference (migration 265) holds each user's view-bar layout (hidden + order doc) per surface container, last-write-wins. CRUD + GET/PUT preference endpoints are gated on the saved_issue_views_v1 feature flag (404 while off, key published to the frontend config). Authorization: private views 404 for everyone but the owner; shared views editable by owner or workspace admin; updates use expected_revision -> 409. Co-Authored-By: Claude Fable 5 <[email protected]> * feat(core): saved issue views client core (MUL-4796) - zod schemas + parseWithFallback for views and preferences (definition blobs stay loose records interpreted per definition_version), with malformed-response tests per the API-compat rules - issue-views module: list/create/update/delete + preference queries with wsId-scoped keys; preference upsert is optimistic (toggle-grade rollback) - active-view store (zustand client state, URL is the durable carrier) and useActiveIssueView with missing-view detection - baseline.ts: enum-sanitized FilterSnapshot of a view's query for value-level locking, delta chips, and baseline-aware resets - view-store: clearFilterDimension + resetFiltersTo actions; surface store seeding routes server blobs through mergeViewStatePersisted Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issues): three-bar priority icons with a distinct urgent badge low/medium/high share one three-bar frame (fill level = severity, ghost bars at 35%); urgent breaks out of the scale as a filled badge with an exclamation cutout — it is an interrupt, not a fourth bar. All glyphs share the same 2-14 optical frame. Board card: PickerWrapper's bare block div gave its inline-flex trigger a line box (line-height 24px > 20px button), floating icons above the text midline — the wrappers are flex containers now, and the priority trigger gets a constant size-5 box so the row's rhythm doesn't depend on the glyph. Co-Authored-By: Claude Fable 5 <[email protected]> * feat(views): saved views UI — view bar, save/edit dialog, locked view mode (MUL-4796) View bar replaces the built-in tab row on Issues and My Issues: built-in tabs and saved views render as one flat, per-user ordered row that wraps instead of overflowing, with a menu for new-view / manage. The manage dialog does drag reorder (translate-only transform, vertical axis lock), show/hide (anchor built-in unhidable), and edit/delete for owned views. Open-view semantics: the view's query is the baseline — its values render checked-and-disabled in the filter menu, never as chips; chips show only the user's additions and every reset path (chip x, clear, menu reset, filtered-empty CTA, board/swimlane hide-column) returns to the baseline, not to empty. Display seeds once per user (view:<id> surface key) and is free afterwards. Built-in tab click exits; deleted/revoked views fall back with a toast; ?view= deep links sync on web via a platform hook. Save dialog doubles as the edit dialog (PATCH with expected_revision -> 409 toast); manager edits seed from the view definition, in-view edits from the live panel. Filter chips bar, extracted IssueFilterMenu (virtual frozen anchor), and the reworked display panel round out the header. Also: filter menu consistency fixes (closeOnClick, calendar apply, TableColumnPicker checkbox items), SelectGroup padding, dropdown anchor passthrough, four-locale i18n. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(views): neutralize workspace tab filter while a saved view is open The Members/Agents tabs are a coarse assignee-type filter carried in the surface scope, outside the filter-menu dimensions a view can express. It kept applying underneath an open view, so the same shared view returned different rows depending on which tab the user stood on when opening it — with nothing on screen to explain the difference (tabs dimmed, no chip). While a view is active the workspace scope now resolves to actorKind "all": a view's results are defined by the view alone, matching what the dimmed tabs already imply. Deliberately NOT captured into saved queries — a view condition must be visible and lockable in the filter menu, and assignee-type has no menu representation (add the menu dimension first if that need ever materializes; the loose schema takes the field additively). Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issue-views): workspace scope_variant captures and applies the assignee-type tab A workspace view now saves which built-in tab it was created from (members/agents, NULL = all) as scope_variant, alongside the existing my-scope variants. The save dialog shows a scope selector so the variant can be switched later; scope_type stays immutable. While a view is open, its own variant drives the workspace scope's assignee-type axis instead of whichever tab the user stood on. - migration 266 widens the scope_variant CHECKs (members/agents on workspace, NULL = all; my keeps its four variants; project stays NULL) - UpdateIssueView persists scope_variant; handler validates the variant against the view's scope_type on create and update - issue-surface applies activeView.scope_variant to effectiveScope, replacing the blanket tab neutralization - issues-header seeds the dialog scope from the live tab Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issue-views): project pages get a real assignee-type axis and clearer variant copy The Members/Agents tabs on project pages were decorative: they wrote the global tab store but the project query never read it. The project scope now carries actorKind like the workspace scope (assignee_types composes with project_id server-side), so the tabs filter for real, and project views join the same optional scope_variant vocabulary — saved from the tab, switchable in the dialog, applied while the view is open. - migration 266 (unmerged) widened in place: project allows members/agents, NULL = all - handler treats workspace and project variants identically; new TestProjectIssueViewVariant covers persist / normalize / reject - project scope key gains a per-tab segment (members/agents) so each tab keeps its own display state; the unrestricted tab keeps the old key - save dialog shows the variant selector for project views; the surface now provides the dialog's default variant directly (header-side injection removed) - variant labels spell out the meaning (Assigned to members/agents, Any assignee, Any relation) and project hints are variant-aware, x4 locales Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-table): project table scope honors assignee_types The table/rows channel dropped the Members/Agents narrowing on project pages: the client's project scope never sent assignee_types and the server's project branch never compiled it, so switching tabs only changed the highlight while table rows stayed put (the board/list channel already filtered). Both sides now share the workspace scope's optional assignee-type narrowing, with a compile test covering the predicate and the invalid-type rejection. Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(issues): single assignee-type mapping + per-page tab state One core helper pair (assigneeTypesForActorKind / actorKindForViewVariant) replaces the four inlined tab->assignee_types literal mappings across query-plan, the table spec compiler, and the view-variant application. The assignee-type tab store becomes page-keyed (issues / project:<id>, persisted with a v0 migration carrying the old global tab to the Issues page): switching tabs inside a project no longer drags the Issues page or other projects along. The save-view dialog's default variant now comes from the header's own page tab, restoring the /issues default that the surface-side injection had dropped. Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(issues): converge all issue surfaces onto the table query channel The legacy list/grouped query layer duplicated the table channel's scope semantics behind enable-conditions that were provably always false: list and status-board ride server status branches, every non-status grouping and swimlane ride server group branches, and the assignee-board / per-status load-more / flat-list / flat-export / client-side my:any 3-request merge fallbacks could never execute. Delete the whole layer: - queries: myIssueListOptions, myIssueAssigneeGroupsOptions, issueAssigneeGroupsOptions, issueFlatListOptions/ExportOptions, fetchAllFlatPages / fetchAllMyFlatIssues / fetchAllMyFirstPages / fetchAllMyAssigneeGroups, compareIssuesForSort (server owns ordering) - mutations: useLoadMoreByStatus, useLoadMoreByAssigneeGroup - board/swimlane: the never-taken client-paginated columns, property pool loader, hidden-column client counts, legacy footer rows - surface data hook / controller: dead queries and passthrough fields - gantt keeps its scheduled-only endpoint and now compiles the assignee-type tab into it, restoring the change alongside the cleanup query-plan shrinks to the scope's non-Table residue: scopeKey, the gantt filter, and create defaults. issueKeys and cache invalidation prefixes are untouched; mobile's own data layer is unaffected. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issues): review fixes — active view's variant wins in save dialog, stable dialog scope, dead mocks Post-review pass on the convergence refactor: - The save dialog's default variant now prefers the OPEN view's scope_variant over the page tab: saving a copy while an agents-scoped view is open must describe the rows on screen, not the tab underneath. - The dialog scope prop is memoized on primitive projections — a fresh object per header render re-armed the dialog's draft-reset effect, so any background refetch wiped a half-typed name. - Tests: persist migration coverage for the page-keyed tab store; gantt assignee_types reaches the request and forks the cache key. - Dead legacy load-more mocks removed from three view test files; stale hidden-columns doc comment; unknown hidden-column totals render no count instead of a blank; my-relation plan branch is return-exhaustive; leftover blank lines. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issues): keep the grabbing cursor for the whole row drag in manage views The handle's cursor classes only apply while the pointer hovers the handle — the vertical-locked drag loses it immediately, and buttons the pointer crosses flip it to pointer. Mirror the sidebar-resize cursor contract: an html[data-dnd-dragging] rule forces cursor: grabbing (and no text selection) document-wide for the drag's duration, toggled by the DndContext lifecycle with an unmount safety net. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issues): my views apply their relation at the query layer, not by switching the tab Opening a my-view used to overwrite the user's relation tab with the view's variant (VARIANT_TO_TAB), so when an open view vanished (deleted / access revoked) the fallback landed on the view's tab instead of where the user actually was. Views are now applied the same way on all three surfaces: while a view is open, effectiveScope substitutes the view's variant — assignee-type for workspace/project, relation for my — and the user's own tab state is never touched. myRelationForViewVariant joins its sibling mapping in scope.ts with unit coverage. Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issues): ease the save-view dialog's expand and validation transitions The display-defaults section snapped open/closed and the name-required hint popped in. The panel now runs the Base UI height transition (--collapsible-panel-height + data-starting/ending-style — the tw-animate collapsible keyframes only know Radix's variable, so the accordion's animate-* classes cannot be reused here), and the hint eases in with the house animate-in vocabulary. Scoped to this dialog: the shared Collapsible primitive stays untouched because chat/transcript panels grow while open and a pinned panel height would clip them. Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issues): filter chips ease in Chips popped into the bar and the save-view dialog's embedded list with no transition. Each chip now enters with the house fade+zoom vocabulary, keyed per dimension so value edits inside an existing chip don't re-animate. No exit animation by design. Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issue-views): drag-reorder the view bar, tab context menu, pin views to sidebar The bar's tabs (built-ins and saved views alike) now reorder by drag, writing the same per-user preference document the manage dialog edits — one list, two editors. Saved-view tabs gain a context menu: - Edit: visible to everyone, disabled without manage permission (the greyed row is the signal); Delete: rendered only for the owner or a workspace admin on shared views — mirroring the server's canManageIssueView — and routed through the shared confirm dialog now extracted from the manage dialog. The manage dialog's own affordances follow the same widened rule (admins included). - Pin to sidebar: pinned_item grows a 'view' type (migration 267; create validates read access so foreign private views 404). Sidebar pin rows resolve the view's name via the new GET-by-id options, render the view-bar icon, activate the view in the store and navigate to its owning surface on click. Flag-off clients keep view pins dormant instead of auto-unpinning; deleted views follow the existing 404-auto-unpin pattern. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): review fixes — guard preference wipes, never auto-unpin views Two data-loss paths from review, plus hardening: - A drag landing before the views list loaded pruned the preference document against an empty list, silently erasing every saved-view entry from order and hidden. savePrefs now refuses to write until the list has actually loaded (viewsReady threads down from useActiveIssueView). - View pins are exempt from 404-auto-unpin: the detail endpoint also 404s when the feature flag is switched off server-side, and a client that booted with the flag on cannot tell the difference — a flag flip would have deleted every view pin permanently. Deleted views' rows hide instead. - POST /api/pins with item_type=view now sits behind the same feature gate as every other view endpoint. - SortableBarTab: drop dnd-kit's aria attributes (they nested a second focusable role=button around the tab), move the wasDragged ref into an effect, and clear it after a cancelled drag so the next real click isn't swallowed. - Sidebar view pins carry ?view= in their href so a web reload stays on the view; scope resolution collapsed to one branch driving both the path and the container key. - Malformed GET /api/issue-views/{id} responses pinned by test as null (hide the row), never an error (which alone may unpin). Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issue-views): single-row view bar with overflow popover The bar no longer wraps: tabs that don't fit collapse into a trailing popover, Linear-style, with two differences — built-ins are first-class orderable entries, and the whole list (hidden rows dimmed) lives in the pulled-down panel with drag reorder, per-row actions (edit/delete under the same permission rule, pin, hide/show) and a new-view entry. - useSingleRowFit (views/common): hidden mirror row measures natural tab widths; ResizeObserver + change-guarded per-commit remeasure computes the fitting prefix with trigger reservation. First reusable collapse-into-menu primitive in the repo. - The open view is always visible: when it overflows it takes the trigger slot, showing its name + chevron (wider reservation; the two-pass promotion settle is monotonic, no oscillation). - Every tab now shares one max width (max-w-40) and truncates with the full name in tooltips/rows. - ManageViewsDialog retired: the popover carries all of its duties (reorder, hide/show incl. built-ins, edit/delete); hiding stays impossible for the anchor built-in and still exits a hidden open view. DeleteViewConfirm and ViewBarItem move to view-bar-popover.tsx; orphaned i18n keys dropped, row action keys added x4 locales. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): restore [layers] menu + manage dialog; 'more' appears only on overflow; fix zero-width bar The single-row rework overreached: it replaced the existing new/manage entries and dialog with an all-in-one popover, and the bar's container collapsed to zero width (content-sized parent + measurement feedback), rendering an empty header. Back to the intended shape: - The [layers] menu (new view / manage views) and the manage dialog are restored exactly as before; DeleteViewConfirm and ViewBarItem stay shared from view-bar-popover. - The 'more' trigger exists ONLY while tabs overflow, showing just the overflowed entries (click to open; per-row edit/delete/pin/hide with the same permission rules — those tabs have no rendered surface to right-click). The open view still takes the trigger slot when it overflows. - Header view-bar slots gain flex-1 so the fit measures a real width; reserve tiers (menu / +more / +promoted) settle monotonically. Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issue-views): list panel shows ALL tabs with drag reorder; trigger always visible The pulled-down panel now lists every bar entry (built-ins included) in bar order with vertical drag reorder — since the bar renders the longest fitting prefix, reordering in the panel directly decides what sits on the bar versus tucks away, with a separator marking the current fold. The trigger no longer appears/disappears with overflow: it is a fixed part of the bar's right side (still replaced by the open view's own tab when that view overflows). Row actions (edit/delete/pin/hide) and the [layers] new/manage entries are unchanged. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): kill the post-drop bounce with a synchronous order mirror The preference mutation is optimistic but its onMutate is async: on drop, dnd-kit clears the drag transform in the same frame while the reordered list lands a beat later, so the dropped tab flashed back to its old slot and then jumped — the manage dialog never bounced because it mirrors the new order synchronously in the drag handler. Both bar drag and panel drag now route through applyMove: set a local order mirror in the same tick, persist, and clear the mirror once the preference data catches up. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): panel row menu is always visible, not hover-revealed Hover-reveal made the three-dot row menu unreachable on touch devices; it is a primary affordance in the list panel, so it renders always. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): long view names truncate/wrap everywhere The manage dialog's row strip is a grid item — min-width:auto let an 80-char name blow past the dialog frame, carrying the row controls with it. Pin the wrapper (min-w-0 + overflow-hidden) and the rows (min-w-0) so labels truncate as designed. The delete confirmation and the save dialog's scope hint interpolate names/titles into prose — break-words so an unbroken name wraps instead of overflowing. Audited every other name surface (bar tabs, promoted trigger, panel rows, sidebar pins, context menus): all already bounded. Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(issue-views): remove the saved_issue_views_v1 feature flag The feature ships unconditionally. The flag never reached any released client, so — unlike the retired flags that live on as always-on compat keys — the key is removed outright on both sides: - server: the issueViewsEnabled 404 gate drops from every view endpoint, the preference endpoints and the view pin type; the key leaves the registry and the frontend-public list; gate tests and the per-test enable helper go with it - frontend: the === true checks, the pre-view-bar fallback tab rows in both headers, and useActiveIssueView's enabled parameter are deleted; the view bar is now the only rendering - compat holds by construction: a NEW client against an OLD backend gets 404s on the list/preference queries → empty views, viewsReady stays false (no preference writes), sidebar view pins hide without auto-unpinning (comment updated to name this as the reason); an OLD client against the NEW backend reads an absent flag as false and keeps the feature hidden Full-suite run also surfaced that the workspace deletion manifest never classified issue_view / issue_view_preference: both now fall with the other issue roots in DeleteWorkspaceIssueRoots (no-FK policy: explicit teardown), and the manifest records the decision. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): fit reserve applies even when every tab fits The all-fit fast path skipped the reserve — a leftover from when the overflow trigger only rendered on overflow. With the list trigger and the [layers] menu now permanent, a row whose tabs exactly filled the container declared everything fitting and overflow-hidden clipped the trailing chrome. The reserve is now subtracted unconditionally. Co-Authored-By: Claude Fable 5 <[email protected]> * feat(issue-views): a freshly created view opens itself Creating a view now activates it on its surface immediately — the view you just saved is the one you meant to be looking at. Skipped only when the create response fails to parse (the list refetch still shows it). Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): creating a view no longer bounces the surface to the default tab Activation raced the list invalidation: the fresh view's id was set active before the refetched list contained it, the stale list read as 'view deleted', and the missing-view fallback kicked the surface back to the anchor tab (with an 'unavailable' toast). Two-sided fix: the create mutation seeds the created view into its scope's list cache before invalidating, and the missing verdict now requires the list to not be mid-refetch. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): release-gate fixes — rollback safety, limits, old-client pin compat, revoke sweep Addresses the pre-ship review blockers: - CI styles: text-muted-foreground/70 -> text-faint-foreground (solid tone rule) in the header hint and the dialog my-note; the chip emoji stack's text-[9px] -> text-micro (type-scale rule). apps/web text-contrast + type-scale suites pass. - migration 269 down normalizes workspace/project members/agents variants to NULL before restoring the old constraints — verified on a scratch DB with real variant data (up -> write -> down). - view pins are withheld from the legacy /api/pins contract unless the client opts in (?include=view): old Desktop builds classified any non-issue pin as a project pin and permanently auto-unpinned it on 404. New clients opt in; regression test pins one and asserts the legacy list never leaks it. - write hardening: MaxBytesReader (128KB) on view create/update and preference PUT; isJSONObject rejects JSON null (was a DB-CHECK 500); per-owner view quota (100/workspace); list query LIMIT 200. Boundary tests for null blob, oversized body, and quota. - member revoke now sweeps the departed member's PRIVATE views and their view-bar preferences in the same transaction (shared views stay, same rule as private quick actions) — the migration comments promised this; test covers private-gone/shared-stays/prefs-gone. - sidebar comment no longer claims project ?view= reload support. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(issue-views): deleting a view sweeps its sidebar pins on every path View pins never auto-unpin client-side (old-backend safety), so a pin whose view is gone was invisible in the UI and unremovable forever — and could leave an empty-looking Pinned group. All three deletion paths now sweep matching pinned_item rows atomically in the same statement via CTEs: direct view delete, project deletion (its scoped views), and member revoke (their private views). Regression tests cover each path. Co-Authored-By: Claude Fable 5 <[email protected]> --------- Co-authored-by: Claude Fable 5 <[email protected]> |
||
|
|
98072e2e56 |
fix(issues): filter working agents by active task issues (#5839)
Co-authored-by: Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
423a5c59cb |
MUL-5200: unify working-agent filters across issue views (#5819)
* fix(issues): query workspace working agents independently Co-authored-by: multica-agent <[email protected]> * feat(agents): filter working agents by source type Co-authored-by: multica-agent <[email protected]> * feat(issues): scope working agents to My Issues Co-authored-by: multica-agent <[email protected]> * test(agents): cover My Issues squad relations Co-authored-by: multica-agent <[email protected]> * fix(issues): unify working-agent filters across views Co-authored-by: multica-agent <[email protected]> * fix(issues): preserve empty working-agent filters Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
40f9ecdd56 |
MUL-5202: unify Issue Query across List, Board, and Swimlane (#5820)
* MUL-5202: migrate status issue surfaces to table query Co-authored-by: multica-agent <[email protected]> * MUL-5202: unify grouped issue surfaces Co-authored-by: multica-agent <[email protected]> * MUL-5202: cover move safety boundaries Co-authored-by: multica-agent <[email protected]> * MUL-5202: preserve server swimlane semantics Co-authored-by: multica-agent <[email protected]> * MUL-5202: keep grouped surface facets exact Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8065cead85 |
MUL-5198: Restore server-backed issue table grouping (MUL-5100) (#5817)
* revert(issues): restore server-backed table grouping Co-authored-by: multica-agent <[email protected]> * test(skills): stabilize import completion coverage Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
4dc47ef113 |
Revert "MUL-5100: Move issue table grouping to the server" (#5777)
This reverts commit
|
||
|
|
d43e500ff6 |
MUL-5100: Move issue table grouping to the server
Merge approved after review; CI checks are green. |