Skip to content

feat: modular background architecture, keyboard shortcuts, and dev tooling (v0.4.0)#16

Merged
fathiraz merged 7 commits intomainfrom
feat/v0.4.0-modular-architecture
Apr 7, 2026
Merged

feat: modular background architecture, keyboard shortcuts, and dev tooling (v0.4.0)#16
fathiraz merged 7 commits intomainfrom
feat/v0.4.0-modular-architecture

Conversation

@fathiraz
Copy link
Copy Markdown
Owner

@fathiraz fathiraz commented Apr 7, 2026

Summary

  • Refactored monolithic background service worker into 6 domain-specific handler modules
  • Added keyboard shortcut registry with platform-aware help overlay (press ?)
  • Lazy-loaded bulk operation modals via React.lazy() + Suspense for faster initial load
  • Set up ESLint 9, Prettier, Vitest, Husky pre-commit hooks
  • Added unit test suite achieving 53% statement coverage on src/lib/**

Architecture

Background service split: background/index.ts → slim orchestrator delegating to:

Module Responsibility
handlers/bulk-handlers.ts Bulk update, close, open, delete, reorder, lock, pin
handlers/sprint-handlers.ts Sprint status, progress, end sprint
handlers/duplicate-handlers.ts Deep issue duplication with relationships
handlers/field-handlers.ts Repo metadata, project fields, reorder context
handlers/hierarchy-handlers.ts Parent/sub-issue relationships, previews
handlers/config-handlers.ts PAT validation, options page

Shared infrastructure: cache.ts (TTL caches), concurrency.ts (operation guards), helpers.ts (utilities), types.ts (interfaces)

New Features

  • Keyboard shortcuts: Centralized registry with /Ctrl platform detection, conflict warnings, and help overlay
  • Lazy modals: React.lazy() code-splitting for BulkEditWizard, BulkDuplicateModal, BulkRenameModal, BulkMoveModal, BulkRandomAssignModal
  • Dev tooling: ESLint 9 flat config, Prettier, Vitest 4.1 with happy-dom, Husky + lint-staged, coverage badge script

Test Coverage

  • 12 test files, 122 tests
  • 53% statement coverage on src/lib/**
  • Key modules tested: keyboard, selection-store, toast-store, queue (with retry), sprint-utils, graphql client, project-table-dom, checkbox-portal-store

Test Plan

  • pnpm test — 122 tests pass
  • pnpm test:coverage — 53% statement coverage
  • pnpm build — clean production build
  • Load extension in Chrome → bulk operations work
  • Press ? → keyboard help overlay displays shortcuts
  • Verify lazy modals load on demand

🤖 Generated with Claude Code


Summary by cubic

Modularized the background worker, added a global keyboard shortcut system with a ? help overlay, and lazy‑loaded bulk modals for faster startup. Also improved dev tooling/tests and fixed cross‑platform meta shortcuts on Windows/Linux plus a duplicate‑flow guard (removed double release).

  • New Features

    • Keyboard shortcuts: central registry with platform-aware hints; press ? to see all shortcuts.
    • Lazy‑loaded bulk modals via React.lazy() + Suspense for faster initial load.
    • UI polish across bulk modals, sprint panel, queue tracker, and toasts.
  • Refactors

    • Background split into domain handlers with shared caches, concurrency guards, helpers, and typed GraphQL responses.
    • Dev tooling: eslint 9 flat config, prettier, vitest with @vitest/coverage-v8, husky + lint-staged, coverage badge script.
    • Tests: 122 tests with ~53% statement coverage on src/lib/**.

Written for commit e8f2a61. Summary will update on new commits.

fathiraz added 4 commits April 8, 2026 00:48
Set up developer experience infrastructure:
- ESLint 9 flat config with TypeScript-ESLint and React Hooks plugins
- Prettier with singleQuote and 100 char width
- Vitest 4.1 with happy-dom environment and v8 coverage
- Husky + lint-staged for pre-commit formatting/linting
- Coverage badge auto-update script
- Version bump to 0.4.0
… worker

Split the single background.ts (3000+ lines) into focused modules:
- handlers/bulk-handlers.ts — bulk update, close, open, delete, reorder, lock, pin
- handlers/sprint-handlers.ts — sprint status, progress, end sprint
- handlers/duplicate-handlers.ts — deep issue duplication with relationships
- handlers/field-handlers.ts — repo metadata, project fields, reorder context
- handlers/hierarchy-handlers.ts — parent/sub-issue relationships, previews
- handlers/config-handlers.ts — PAT validation, options page

Shared infrastructure extracted:
- cache.ts — TTL-based caches (resolved items, hierarchy, fields, sprint progress)
- concurrency.ts — operation guards (max 3 bulk/duplicate, max 1 sprint end)
- helpers.ts — shared utilities (rate-limit retry, queue broadcast, field resolution)
- types.ts — TypeScript interfaces for GraphQL responses
New features:
- Keyboard shortcut registry with platform-aware symbols (⌘/Ctrl)
- Help overlay (press ?) showing all registered shortcuts by context
- Lazy-loaded bulk operation modals via React.lazy() + Suspense

UI improvements across bulk modals, sprint tracking, queue tracker,
toast notifications, onboarding coach, and shared UI primitives.
Updated content scripts, popup, and options page.

Added Firefox Add-ons badge and updated README with store links.
New test files:
- keyboard.test.ts — shortcut registry, format, conflict detection, keydown handling
- selection-store.test.ts — toggle, batch, clear, subscribe, focus
- toast-store.test.ts — show, dismiss, auto-dismiss timer, MAX_TOASTS limit
- z-index.test.ts — constant ordering verification
- checkbox-portal-store.test.ts — add/deduplicate entries, subscribe
- project-table-dom.test.ts — extractItemId, isEditableTarget, getAllInjectedItemIds
- primer-live-region-stub.test.ts — stub function coverage
- debug-logger.test.ts — conditional logging, error always logs

Improved existing tests:
- queue.test.ts — rate-limit retry (403/429), task detail broadcast, failed items
- sprint-utils.test.ts — injectSprintFilter DOM manipulation

12 test files, 122 tests, 53% statement coverage on src/lib/**
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor auto review

Found 1 actionable issue(s) on changed lines.

  • Found a correctness issue in keyboard shortcut modifier matching: ctrl shortcuts will never trigger on non-macOS platforms.

Generated automatically when this PR was submitted using Cursor CLI with --model auto.

Comment thread src/lib/keyboard.ts
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 issues found across 94 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed. We prioritized the most important files first.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="README.md">

<violation number="1" location="README.md:22">
P3: The README badge incorrectly advertises React 18 while the project depends on React 19.</violation>
</file>

<file name="scripts/update-coverage-badge.mjs">

<violation number="1" location="scripts/update-coverage-badge.mjs:59">
P2: The badge marker regex is not multiline-safe, so badge updates can silently fail when the marker block spans lines.</violation>
</file>

<file name="src/entries/background/handlers/sprint-handlers.ts">

<violation number="1" location="src/entries/background/handlers/sprint-handlers.ts:281">
P2: Silent rejection when sprint-end concurrency limit is reached. The handler returns `undefined` without broadcasting any status to the UI, leaving the user with no feedback. Consider broadcasting an error/status message or returning a structured error response.</violation>

<violation number="2" location="src/entries/background/handlers/sprint-handlers.ts:388">
P1: Inconsistent "done" classification between `getSprintProgress` and `endSprint`. When `notStartedOptionId` is configured, `isItemDone` in `getSprintProgress` treats everything except "Not Started" as done, but the `endSprint` filter only recognizes the exact `doneOptionId`. This means items the progress view reports as completed could still get moved to the next sprint.</violation>
</file>

<file name="src/entries/background/handlers/hierarchy-handlers.ts">

<violation number="1" location="src/entries/background/handlers/hierarchy-handlers.ts:74">
P2: Fetch `blockedBy` and `blocking` in parallel to avoid unnecessary preview latency.</violation>
</file>

<file name="src/components/ui/markdown-textarea.tsx">

<violation number="1" location="src/components/ui/markdown-textarea.tsx:101">
P2: Add `type="button"` to these UI buttons to prevent unintended form submission when used inside a form.</violation>
</file>

<file name="src/components/queue-tracker.tsx">

<violation number="1" location="src/components/queue-tracker.tsx:225">
P2: Handle `sendMessage` failure in Retry Failed action to avoid unhandled promise rejections and silent retry failures.</violation>
</file>

<file name="src/entries/background/handlers/duplicate-handlers.ts">

<violation number="1" location="src/entries/background/handlers/duplicate-handlers.ts:66">
P1: Early returns on error leave the progress UI stuck at "Fetching item…" with no completion broadcast. When `details.node` is missing or the item isn't an issue, the function returns without notifying the UI, so the progress indicator never clears. Broadcast an error/done status before each early return.</violation>
</file>

<file name="src/entries/background/handlers/bulk-handlers.ts">

<violation number="1" location="src/entries/background/handlers/bulk-handlers.ts:1240">
P2: Off-by-one in progress status: when `state.completed === reorderOps.length` on the final callback, this displays "Moving item N+1 of N…". The sibling `bulkReorder` handler correctly guards against this with a ternary check.</violation>
</file>

<file name="src/lib/keyboard.ts">

<violation number="1" location="src/lib/keyboard.ts:82">
P2: Normalize optional modifier flags in conflict detection; otherwise equivalent shortcuts (`undefined` vs `false`) can bypass duplicate/conflict warnings.</violation>
</file>

<file name="src/lib/queue-store.ts">

<violation number="1" location="src/lib/queue-store.ts:112">
P2: Reset failure metadata when a new run starts on an existing completed process; otherwise stale `failedItems`/`retryContext` can leak into the next operation.</violation>
</file>

<file name="src/components/bulk/bulk-actions-bar.tsx">

<violation number="1" location="src/components/bulk/bulk-actions-bar.tsx:269">
P1: The `?` help shortcut will never fire on standard keyboard layouts. Pressing `?` requires Shift, but `modifiers: {}` expects `shiftKey` to be `false`. The strict equality check in `matchesModifiers` (`wantsShift === e.shiftKey`) rejects the keystroke.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/entries/background/handlers/sprint-handlers.ts
Comment thread src/entries/background/handlers/duplicate-handlers.ts
Comment thread src/components/bulk/bulk-actions-bar.tsx Outdated
Comment thread scripts/update-coverage-badge.mjs Outdated
Comment thread src/entries/background/handlers/sprint-handlers.ts
Comment thread src/components/queue-tracker.tsx
Comment thread src/entries/background/handlers/bulk-handlers.ts Outdated
Comment thread src/lib/keyboard.ts Outdated
Comment thread src/lib/queue-store.ts Outdated
Comment thread README.md Outdated
fathiraz added 2 commits April 8, 2026 04:58
Outer array typed as `readonly PortalEntry[][]` prevented `.push()` calls,
breaking typecheck in CI.
…, safety guards

- Fix ctrl shortcuts on non-macOS (was hardcoded false)
- Normalize modifier flags in conflict detection (undefined vs false)
- Add shift modifier to ? help shortcut
- Align endSprint done classification with getSprintProgress (notStartedOptionId)
- Return structured error on sprint-end concurrency rejection
- Broadcast done status on duplicate early-return errors
- Add dotall flag to coverage badge regex
- Parallelize blockedBy/blocking read queries
- Add type="button" to markdown-textarea buttons
- Handle sendMessage failure in retry action
- Fix off-by-one in reorder progress display
- Reset stale failedItems/retryContext on new queue run
- Fix React badge version 18 → 19
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 13 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/lib/keyboard.ts">

<violation number="1" location="src/lib/keyboard.ts:37">
P1: This change makes `meta` shortcuts fail on non-Mac because pressing Ctrl now also forces `ctrl` to match. Meta-only shortcuts (the cross-platform pattern) will no longer trigger on Windows/Linux.</violation>
</file>

<file name="src/entries/background/handlers/duplicate-handlers.ts">

<violation number="1" location="src/entries/background/handlers/duplicate-handlers.ts:78">
P1: `releaseDuplicate()` is called in early returns and again in `finally`, causing double-decrement of the duplicate concurrency counter.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/lib/keyboard.ts Outdated
Comment thread src/entries/background/handlers/duplicate-handlers.ts Outdated
…cate

- keyboard.ts: ctrlPressed was always e.ctrlKey on all platforms, so on
  Windows/Linux pressing Ctrl set both metaPressed and ctrlPressed to true,
  causing meta-only shortcuts ({meta:true, ctrl:false}) to never match.
  Changed to `isMac ? e.ctrlKey : false` so Ctrl is exclusively consumed
  as the meta equivalent on non-Mac platforms.

- duplicate-handlers.ts: three early-return paths (fetch error, !source,
  !issue.title) each called releaseDuplicate() before returning, but the
  outer finally block also calls it unconditionally. Removed the three
  redundant calls; finally now owns the single release.
@fathiraz fathiraz merged commit fa47701 into main Apr 7, 2026
3 checks passed
@fathiraz fathiraz deleted the feat/v0.4.0-modular-architecture branch April 7, 2026 22:32
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