Skip to content

feat(tasks): rework the Jira board view and rank priority by severity - #41

Merged
nplez1 merged 2 commits into
nplez1/mainfrom
nplez1/Remove-Team
Sep 29, 2026
Merged

nplez1 merged 2 commits into
nplez1/mainfrom
nplez1/Remove-Team

Conversation

@nplez1

@nplez1 nplez1 commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​654 $\color{#cf222e}{\Huge{\mathbf{−}}}$​334 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​320
Prod 35 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​860 $\color{#cf222e}{\Huge{\mathbf{−}}}$​660 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​200

ELI5

The Jira board in Tasks reloaded from scratch every time you came back to it, and it forgot everything you had set up. It also had a "Team" filter that needed two extra settings to work and wasn't being used. This makes the board come back instantly, refresh quietly behind the scenes, remember your choices, and gives it a search box. It also makes urgent work obvious at a glance: a Blocker tints its whole row red, a Critical gets a red chip, and a Major gets an orange one.

What Changed

Board view

  • The Team filter is gone, along with the "Team field" and "Team value" settings that existed only to feed it. The board filter is now just Me or All.
  • The board opens on Me (your own work) rather than on the whole board filter. When the board's site has no resolvable viewer account it shows everything, instead of an empty board behind a disabled button.
  • The board is cached. Switching to another workspace and back paints the last snapshot immediately and refreshes behind it, instead of showing a loading screen every time.
  • The board has a filter box again. The provider filter row (presets + JQL box) is hidden while the board is shown, which had left the board with no way to narrow issues. The board now has its own box that filters the loaded issues by key, summary, assignee, status, type, or label — no refetch.
  • Board-vs-list, sprint-vs-backlog, Me/All, and the filter text are remembered per device, across workspace switches and app restarts.

Priority (severity)

  • Blocker and Critical get a red chip; Major gets orange; everything below (medium, normal, low, minor, lowest, trivial, and any custom scheme) stays muted, so a renamed priority scheme is never mislabelled.
  • A Blocker also washes its whole row (issue list) or card (board), and keeps that wash on hover and while selected — a chip alone was too easy to skim past on a dense board.

Mechanism

  • New module-level board cache keyed by siteId:boardId (30 minute TTL, three boards) holding the overview and the sprint/backlog pages. The hooks seed from it synchronously, so a cached board paints on the first render and the refetch runs silently behind it.
  • New per-device preference store in localStorage for the board view, mirroring linear-issue-view-storage.ts.
  • New severity tone module plus a shared priority badge, driven by two new theme tokens (--severity-blocker, --severity-major).
  • Deleting the Team field also deleted the custom-field listing that existed only to populate it: jira:listCustomFields, jira.listCustomFields, the preload and runtime clients, and the main-process query. The generated RPC params catalog is regenerated.

Why

Caching. The Tasks view unmounts when you switch to a workspace, so every return paid for the full board read again. The alternatives were to keep the Tasks view mounted (holds an entire hidden view and its subscriptions alive, and still loses everything on a reload) or to persist board data to disk (serves stale data across restarts, while a Jira board's scope is owned server-side and can change at any time). An in-memory session cache plus a silent revalidate keeps the fast path fast without inventing a stale-data contract.

Where the view preferences live. Not in taskResumeState. That schema is .strict() behind a field-level .catch, so a host that predates a key rejects the whole payload and drops the GitHub and Jira resume state with it. Per-device view preferences already have a precedent for exactly this reason in linear-issue-view-storage.ts, so these follow it.

Removing the Team filter. It required a per-site custom field to be picked and an exact value typed, and the value match was fragile across option and Jira-team field types. With Me and All covering the real use, the settings cost more than the filter earned.

The severity ramp. Blocker and Critical deliberately share the red chip, with the surface wash as the only difference, rather than a four-hue ramp: red/orange/amber/grey put Critical and Major too close together at a glance, and a solid red chip on a red-washed row read as one undifferentiated red block.

Linked Issue

Fixes #

No issue exists for this; this is internal follow-up work on the Jira board view.

Visual Proof

Light and dark, issue list and board cards. Every tier is rendered, so the ramp is visible rather than just the Blocker. The wash applies to the whole row/card.

Before — priority is plain muted text, identical for every tier, and no row or card is tinted:

before.png

After — Blocker washes its whole row and card, Critical shares the red chip, Major is orange, everything below stays muted:

after.png

Both images were rendered from the real component classes against the app's real main.css (compiled with the repo's Tailwind), not from a mockup. They show the priority treatment only; the caching and remembered-view changes have no static visual.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Not manually tested in the app — verifying the board end to end needs a live Jira connection with a scrum board and a Critical-named priority, which this environment does not have. The screenshots above were rendered from the real classes; the tokens were checked for presence in both themes rather than eyeballed in the running app.

Automated coverage added:

  • task-page-jira-priority-tone.test.ts — tier mapping (including case/whitespace and custom schemes), and that Blocker and Critical produce an identical chip tone while only Blocker returns a surface wash, so a future edit cannot quietly diverge them.
  • task-page-jira-grouping.test.ts — renders the issue list with a Blocker, Critical, Major, and unranked issue; asserts the Blocker row is washed, the Critical row is not washed despite the red chip, and each chip reports its data-severity.
  • task-page/Jira/Board.test.tsx — Blocker card wash, cached board renders without a loading state and still revalidates, a settings rerun does not re-show a loader, and the selected sprint survives a remount without mixing sprint pages.
  • task-page-jira-board-cache.test.ts, jira-board-view-storage.test.ts, jira-board-view-preferences.test.ts — TTL/LRU/merge behaviour, storage round-trip and corrupt-payload fallback, and dropping the retired team filter value.

Commands run locally:

pnpm tc                                    # node + cli + web typecheck
pnpm exec oxlint                           # whole repo, clean
pnpm run check:code-quality:changed        # 32 files, 0 new findings
pnpm run check:max-lines-ratchet
pnpm run audit:anti-slop
pnpm test src/renderer/src/components src/renderer/src/assets   # 21,889 pass
pnpm test src/renderer/src/components/task-page src/renderer/src/components/use-task-page src/renderer/src/components/task-page-jira src/renderer/src/components/settings src/renderer/src/runtime/runtime-jira-client.test.ts src/main/jira
pnpm run verify:rpc-params-catalog
pnpm run verify:localization-{catalogs,extraction,coverage}

Full pnpm lint, the whole pnpm test, and pnpm build were not run locally — CI will cover them.

AI Disclosure

The implementation was written with an AI coding assistant (pi) working in this worktree, under review at each step. The verification above is what was actually executed.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Cross-platform: no new platform-dependent behaviour. The persistence uses localStorage; nothing path-, shell-, or shortcut-specific is touched.
  • Remote/SSH and mobile: the changed board state is renderer-local. taskResumeState is deliberately untouched, so there is no new wire surface and no ui.set schema change; the only RPC change is the removal of jira.listCustomFields. Mobile has no consumer of it (verified by grep), so a paired client is unaffected apart from that method no longer existing — an old client that still showed the Team-field picker would see the field list fail, which is consistent with the feature being removed.
  • Backwards compatibility: removing the optional teamFieldId board-issue parameter is non-breaking in both directions, because that schema is a plain z.object that strips unknown keys. Stored settings that still contain jiraTeamFieldId/jiraTeamValue are ignored rather than rejected; settings are merged over defaults in main, with no strict schema.
  • Performance: the board read is now usually skipped on return (cached) and otherwise revalidated silently, so this strictly reduces Jira calls when switching workspaces. Silent revalidation is bounded to three pages, so a deeply paged board cannot turn a return-to-view into an unbounded burst of requests.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Returning to the Jira board always showed a loading screen, even right after
leaving it, and the board forgot every choice made on it. Three causes, all in
the board view's own state:

- The overview and the sprint/backlog pages were refetched from scratch on every
  mount, because the Tasks view unmounts when you switch to a workspace.
- The sprint/backlog tab, the Me/All filter, and the filter query lived only in
  component state, so nothing survived that unmount.
- The provider filter chrome is hidden while the board is shown, which left the
  board with no filter box at all.

Fixes:

- Keep the last board snapshot in a module cache keyed by site/board (30 minute
  TTL, three boards) and seed the hooks from it synchronously, so a cached board
  paints on the first render. The refetch then runs silently behind it, reading
  back the pages that were already loaded so a paged board cannot mix fresh page
  one with stale later pages. Refresh still shows the spinner, and switching
  sprint drops the cached page rather than pairing the new sprint with the old
  one's issues.
- Persist the board/list mode, sprint/backlog tab, Me/All filter, and filter text
  per device in local storage, mirroring the Linear issue-view preferences. They
  stay out of taskResumeState: that schema is strict behind a field-level catch,
  so a host that predates a key rejects the whole payload and drops the GitHub
  and Jira resume state with it.
- Give the board its own filter box, narrowing the loaded issues client-side by
  key, summary, assignee, status, type, or label.
- Open on the viewer's own work (Me) rather than the whole board filter. When the
  board's site has no resolvable viewer account, show everything instead of an
  empty board behind a disabled button.
- Drop the Team filter and its Team field / Team value settings; the board filter
  is now Me/All.

The Team field was the only consumer of the custom-field listing, so
jira:listCustomFields, jira.listCustomFields, the preload and runtime clients, and
the main-process query all go with it, and the generated RPC params catalog is
regenerated.
A Jira issue's priority was plain text in the same muted grey as every other
field, so a blocker read exactly like a trivial task in a dense list and was
easy to skim past on a board.

- Give each priority a severity chip: a coloured bubble plus the tier name, so
  the hue is never the only signal.
- Blocker and Critical share the red chip; Major is orange. Everything below
  (medium, normal, low, minor, lowest, trivial, and any custom scheme) stays
  muted, because a renamed priority scheme must not be mislabelled.
- A blocker additionally washes its whole task surface — row or board card — and
  keeps that wash on hover and while selected, since a chip alone does not stand
  out enough on a board.

The tiers are matched by name against the standard Jira vocabularies
(blocker/highest, critical, major/high). Two new tokens, --severity-blocker and
--severity-major, carry the ramp in both themes; Major no longer borrows
--status-warning, which stays free for its own needs-attention role.
@nplez1
nplez1 merged commit 5398c82 into nplez1/main Sep 29, 2026
41 checks passed
nplez1 added a commit that referenced this pull request Sep 29, 2026
…#41)

* feat(tasks): cache the Jira board and remember its view state

Returning to the Jira board always showed a loading screen, even right after
leaving it, and the board forgot every choice made on it. Three causes, all in
the board view's own state:

- The overview and the sprint/backlog pages were refetched from scratch on every
  mount, because the Tasks view unmounts when you switch to a workspace.
- The sprint/backlog tab, the Me/All filter, and the filter query lived only in
  component state, so nothing survived that unmount.
- The provider filter chrome is hidden while the board is shown, which left the
  board with no filter box at all.

Fixes:

- Keep the last board snapshot in a module cache keyed by site/board (30 minute
  TTL, three boards) and seed the hooks from it synchronously, so a cached board
  paints on the first render. The refetch then runs silently behind it, reading
  back the pages that were already loaded so a paged board cannot mix fresh page
  one with stale later pages. Refresh still shows the spinner, and switching
  sprint drops the cached page rather than pairing the new sprint with the old
  one's issues.
- Persist the board/list mode, sprint/backlog tab, Me/All filter, and filter text
  per device in local storage, mirroring the Linear issue-view preferences. They
  stay out of taskResumeState: that schema is strict behind a field-level catch,
  so a host that predates a key rejects the whole payload and drops the GitHub
  and Jira resume state with it.
- Give the board its own filter box, narrowing the loaded issues client-side by
  key, summary, assignee, status, type, or label.
- Open on the viewer's own work (Me) rather than the whole board filter. When the
  board's site has no resolvable viewer account, show everything instead of an
  empty board behind a disabled button.
- Drop the Team filter and its Team field / Team value settings; the board filter
  is now Me/All.

The Team field was the only consumer of the custom-field listing, so
jira:listCustomFields, jira.listCustomFields, the preload and runtime clients, and
the main-process query all go with it, and the generated RPC params catalog is
regenerated.

* feat(tasks): rank Jira priority by severity and wash blockers

A Jira issue's priority was plain text in the same muted grey as every other
field, so a blocker read exactly like a trivial task in a dense list and was
easy to skim past on a board.

- Give each priority a severity chip: a coloured bubble plus the tier name, so
  the hue is never the only signal.
- Blocker and Critical share the red chip; Major is orange. Everything below
  (medium, normal, low, minor, lowest, trivial, and any custom scheme) stays
  muted, because a renamed priority scheme must not be mislabelled.
- A blocker additionally washes its whole task surface — row or board card — and
  keeps that wash on hover and while selected, since a chip alone does not stand
  out enough on a board.

The tiers are matched by name against the standard Jira vocabularies
(blocker/highest, critical, major/high). Two new tokens, --severity-blocker and
--severity-major, carry the ramp in both themes; Major no longer borrows
--status-warning, which stays free for its own needs-attention role.
nplez1 added a commit that referenced this pull request Oct 1, 2026
…#41)

* feat(tasks): cache the Jira board and remember its view state

Returning to the Jira board always showed a loading screen, even right after
leaving it, and the board forgot every choice made on it. Three causes, all in
the board view's own state:

- The overview and the sprint/backlog pages were refetched from scratch on every
  mount, because the Tasks view unmounts when you switch to a workspace.
- The sprint/backlog tab, the Me/All filter, and the filter query lived only in
  component state, so nothing survived that unmount.
- The provider filter chrome is hidden while the board is shown, which left the
  board with no filter box at all.

Fixes:

- Keep the last board snapshot in a module cache keyed by site/board (30 minute
  TTL, three boards) and seed the hooks from it synchronously, so a cached board
  paints on the first render. The refetch then runs silently behind it, reading
  back the pages that were already loaded so a paged board cannot mix fresh page
  one with stale later pages. Refresh still shows the spinner, and switching
  sprint drops the cached page rather than pairing the new sprint with the old
  one's issues.
- Persist the board/list mode, sprint/backlog tab, Me/All filter, and filter text
  per device in local storage, mirroring the Linear issue-view preferences. They
  stay out of taskResumeState: that schema is strict behind a field-level catch,
  so a host that predates a key rejects the whole payload and drops the GitHub
  and Jira resume state with it.
- Give the board its own filter box, narrowing the loaded issues client-side by
  key, summary, assignee, status, type, or label.
- Open on the viewer's own work (Me) rather than the whole board filter. When the
  board's site has no resolvable viewer account, show everything instead of an
  empty board behind a disabled button.
- Drop the Team filter and its Team field / Team value settings; the board filter
  is now Me/All.

The Team field was the only consumer of the custom-field listing, so
jira:listCustomFields, jira.listCustomFields, the preload and runtime clients, and
the main-process query all go with it, and the generated RPC params catalog is
regenerated.

* feat(tasks): rank Jira priority by severity and wash blockers

A Jira issue's priority was plain text in the same muted grey as every other
field, so a blocker read exactly like a trivial task in a dense list and was
easy to skim past on a board.

- Give each priority a severity chip: a coloured bubble plus the tier name, so
  the hue is never the only signal.
- Blocker and Critical share the red chip; Major is orange. Everything below
  (medium, normal, low, minor, lowest, trivial, and any custom scheme) stays
  muted, because a renamed priority scheme must not be mislabelled.
- A blocker additionally washes its whole task surface — row or board card — and
  keeps that wash on hover and while selected, since a chip alone does not stand
  out enough on a board.

The tiers are matched by name against the standard Jira vocabularies
(blocker/highest, critical, major/high). Two new tokens, --severity-blocker and
--severity-major, carry the ramp in both themes; Major no longer borrows
--status-warning, which stays free for its own needs-attention role.
nplez1 added a commit that referenced this pull request Oct 6, 2026
…#41)

* feat(tasks): cache the Jira board and remember its view state

Returning to the Jira board always showed a loading screen, even right after
leaving it, and the board forgot every choice made on it. Three causes, all in
the board view's own state:

- The overview and the sprint/backlog pages were refetched from scratch on every
  mount, because the Tasks view unmounts when you switch to a workspace.
- The sprint/backlog tab, the Me/All filter, and the filter query lived only in
  component state, so nothing survived that unmount.
- The provider filter chrome is hidden while the board is shown, which left the
  board with no filter box at all.

Fixes:

- Keep the last board snapshot in a module cache keyed by site/board (30 minute
  TTL, three boards) and seed the hooks from it synchronously, so a cached board
  paints on the first render. The refetch then runs silently behind it, reading
  back the pages that were already loaded so a paged board cannot mix fresh page
  one with stale later pages. Refresh still shows the spinner, and switching
  sprint drops the cached page rather than pairing the new sprint with the old
  one's issues.
- Persist the board/list mode, sprint/backlog tab, Me/All filter, and filter text
  per device in local storage, mirroring the Linear issue-view preferences. They
  stay out of taskResumeState: that schema is strict behind a field-level catch,
  so a host that predates a key rejects the whole payload and drops the GitHub
  and Jira resume state with it.
- Give the board its own filter box, narrowing the loaded issues client-side by
  key, summary, assignee, status, type, or label.
- Open on the viewer's own work (Me) rather than the whole board filter. When the
  board's site has no resolvable viewer account, show everything instead of an
  empty board behind a disabled button.
- Drop the Team filter and its Team field / Team value settings; the board filter
  is now Me/All.

The Team field was the only consumer of the custom-field listing, so
jira:listCustomFields, jira.listCustomFields, the preload and runtime clients, and
the main-process query all go with it, and the generated RPC params catalog is
regenerated.

* feat(tasks): rank Jira priority by severity and wash blockers

A Jira issue's priority was plain text in the same muted grey as every other
field, so a blocker read exactly like a trivial task in a dense list and was
easy to skim past on a board.

- Give each priority a severity chip: a coloured bubble plus the tier name, so
  the hue is never the only signal.
- Blocker and Critical share the red chip; Major is orange. Everything below
  (medium, normal, low, minor, lowest, trivial, and any custom scheme) stays
  muted, because a renamed priority scheme must not be mislabelled.
- A blocker additionally washes its whole task surface — row or board card — and
  keeps that wash on hover and while selected, since a chip alone does not stand
  out enough on a board.

The tiers are matched by name against the standard Jira vocabularies
(blocker/highest, critical, major/high). Two new tokens, --severity-blocker and
--severity-major, carry the ramp in both themes; Major no longer borrows
--status-warning, which stays free for its own needs-attention role.
@nplez1
nplez1 deleted the nplez1/Remove-Team branch October 24, 2026 03:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant