Skip to content

feat(my-work): adds my work page, jira and GitHub combined#56

Open
zalos wants to merge 35 commits intomainfrom
feature/my-work
Open

feat(my-work): adds my work page, jira and GitHub combined#56
zalos wants to merge 35 commits intomainfrom
feature/my-work

Conversation

@zalos
Copy link
Copy Markdown
Collaborator

@zalos zalos commented Apr 28, 2026

Initial my work page.

This pull request introduces comprehensive end-to-end (E2E) test coverage for the new "My Work" page, updates screenshot baselines, and significantly strengthens the documented validation and "definition of done" process for development tasks. The changes ensure that new features are properly tested and that contributors explicitly verify passing tests and correct behavior before marking tasks as complete.

Key changes:

E2E Test Coverage

  • Added a new E2E test spec e2e/my-work.spec.ts to cover the "My Work" aggregation page, verifying sidebar navigation, disconnected state handling, and CTA routing for integrations.
  • Updated e2e/screenshot-crawl.spec.ts to include the "My Work" page in the screenshot crawl, capturing its initial (disconnected) state for regression testing.

Screenshot Baselines

Developer Workflow & Validation

  • Strengthened the documented development cycle in .claude/agents/core-developer.md to require that all new or modified tests are run and pass before reporting a task as complete, with explicit instructions for unit, E2E, typecheck, and build validation.
  • Added a clear "Definition of Done" checklist, requiring explicit verification of test execution, typechecking, absence of new console errors, and manual user-flow validation before declaring a task finished.

These changes together improve the reliability of new features, enforce best practices for test-driven development, and help prevent regressions in both functionality and UI.

zalos and others added 30 commits April 26, 2026 14:21
…-time stripping

Adds features.json as the single source of truth for every base-app feature
flag (38 flags, both base and experimental). A small Node generator emits
src/shared/featureFlags.generated.ts which both the main and renderer
processes import for the FeatureFlags type and BUILD_FLAGS defaults.

The Vite config exposes the same data as a __FEATURES__ define literal so
experimental gating becomes a static boolean at build time, letting Rollup
tree-shake disabled experimental code paths out of the production bundle.
Setting CLEARPATH_E2E_EXPERIMENTAL=1 at build time forces every
experimental flag on (used by the experimental-feature e2e crawl).

predev / prebuild / pretest hooks regenerate the TS module so it never
drifts from features.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the duplicated FeatureFlags interface and DEFAULTS constants that
lived in featureFlagHandlers.ts and FeatureFlagContext.tsx — both now
re-export the type from src/shared/featureFlags.generated.ts and seed
defaults from BUILD_FLAGS. Settings UI greys out and disables toggles for
experimental flags whose code is compiled out so users see why the toggle
is inert.

Experimental routes (PR Scores, Backstage Explorer) move to React.lazy
behind __FEATURES__ checks. With the flag off, Rollup drops both the
import call and the page chunk; with CLEARPATH_E2E_EXPERIMENTAL=1 the
chunks return so e2e tests can cover them.

featureFlagHandlers also clamps stored overrides for compiled-out
experimental flags so a stale override from a prior build can't leave
the UI claiming a missing feature is enabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e subfolder

Adds e2e/screenshot-crawl-experimental.spec.ts and a matching wdio config
that builds the app with CLEARPATH_E2E_EXPERIMENTAL=1, navigates to each
experimental-only route, and writes screenshots into the new
e2e/screenshots/baseline/experimental-features/ subfolder so they don't
pollute the default visual baseline.

CI gains a screenshot-regression-experimental job that depends on the
default screenshot job (sequential to avoid a force-push race) and runs
the same compare+promote policy scoped to experimental-features/.

Default wdio.conf.ts excludes the new spec so it only runs via the
dedicated npm scripts:
  npm run e2e:screenshots:experimental
  npm run e2e:screenshots:experimental:update

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n off

When showPrScores / showBackstageExplorer are compiled out, the previous
build made the routes conditional — so navigating to `#/pr-scores` (e.g.
via the PR Scores extension's pinned sidebar link) had no matching child
route and the parent Layout was unmounted, taking the sidebar with it.
This blew up the screenshot crawl mid-run: every Sidebar Pages test
passed, but every subsequent describe failed because the sidebar nav
was gone for the rest of the session.

Always register the experimental paths and let the route element decide
between rendering the lazy page or redirecting to /work. The lazy
import() is still gated by `__FEATURES__.X`, so disabled experimental
chunks remain tree-shaken from the production bundle (verified: no
score-pr / backstage-explorer:get-relationships / RelationshipViewer
strings in out/renderer/assets/index-*.js).

Also addresses first-round AI review comments:
- App.tsx: drop dead reference to non-existent features.spec.ts.
- features.json: remove `$schema` pointing at a file that doesn't exist
  yet (re-add when we author features.schema.json).
- electron.vite.config.ts: clarify BUILD_FLAGS is a generation-time
  literal, not a `__FEATURES__` consumer.
- screenshot-crawl-experimental.spec.ts: assert each route's marker
  text is present before the screenshot, so a misconfigured build (no
  CLEARPATH_E2E_EXPERIMENTAL=1) fails loudly instead of capturing a
  blank fallback.
wdio-visual-service's autoSaveBaseline copies the actual screenshot to
the baseline path with `fs.copyFile`, which fails with ENOENT when the
parent directory doesn't exist. The first experimental run had no
baselines and the `experimental-features/` subfolder hadn't been
created yet — every test failed before the first baseline could be
written, leaving us stuck in a chicken-and-egg state.

Create the directory at config-load time so the first run can write
its initial baselines and let the existing CI promote-and-commit step
take over from there.
The experimental build is invoked with CLEARPATH_E2E_EXPERIMENTAL=1,
which makes the prebuild hook regenerate
src/shared/featureFlags.generated.ts with experimental flags forced on.
After the baseline commit step stages the new screenshots, that
unrelated regen lingers as an unstaged change — and `git rebase`
refuses to run on a dirty tree, so the auto-baseline push died with
"cannot rebase: You have unstaged changes."

Discard the unstaged delta + any untracked build artifacts after the
commit but before the rebase loop. The regular screenshot-regression
job doesn't hit this because its build runs without the env var, so
the regenerated file matches what's committed.
- App.tsx: drop the `ComponentType` annotation. `React.lazy()` returns
  `LazyExoticComponent<...>`, which isn't structurally `ComponentType`.
  Letting TS infer keeps the type honest and unblocks future use of
  the lazy components' static `preload`-style helpers.

- scripts/generate-feature-flags.mjs: rewrite the misleading comment.
  The generator does NOT emit `as const` — `BUILD_FLAGS` is a typed
  Readonly literal whose tree-shakeability comes entirely from the
  Vite `__FEATURES__` define, not from constant inference here.

- electron.vite.config.ts: stop swallowing generator failures. Falling
  back to a stale `featureFlags.generated.ts` while `__FEATURES__` is
  computed fresh from features.json lets the two sources of truth
  drift, which surfaces as runtime UI inconsistency vs. the bundled
  experimental code paths. Always rethrow.
Previous attempt used \`git checkout -- .\` to clean the unstaged
\`featureFlags.generated.ts\` regen, but that re-runs the Git LFS
smudge filter against every tracked binary in the tree. The smudge
choked on 22 LFS-tracked SVG/icon files ("should have been pointers,
but weren't") and exited 1, killing the experimental baseline push.

\`git stash --include-untracked\` is the safer primitive — it captures
unstaged tracked changes + untracked files into a stash without
re-running smudge across the whole worktree. We never pop the stash,
so it dies with the runner.
The previous rebase-loop approach kept colliding with the LFS smudge
filter and the experimental-build's regen of featureFlags.generated.ts.
Since the experimental job runs with \`needs: screenshot-regression\`,
the sibling job has already pushed any auto-baseline commit by the
time our \`actions/checkout@v5\` runs — there's nothing to rebase
onto. Drop the rebase loop and use the same simple
\`git diff --staged --quiet || git commit\` + \`push --force-with-lease\`
pattern the regular screenshot-regression job uses. \`--force-with-lease\`
is happy to push from a dirty working tree as long as the remote ref
matches what we fetched at checkout.
…w fixes

- Remove `ext--pr-scores` from the regular screenshot crawl. The
  PR Scores extension pins a sidebar link to `#/pr-scores`, which is
  now build-time-gated; with the flag off it redirects to /work and
  the captured screenshot is just a duplicate of `work--initial`.
  When the flag is on, screenshot-crawl-experimental.spec.ts already
  captures the page under `experimental-features/`.

- scripts/generate-feature-flags.mjs: rewrite the
  `isExperimentalFlagEnabledAtBuild` comment to admit that the
  computed key lookup is NOT statically replaceable, and tell readers
  to use direct property access on `__FEATURES__` for tree-shaking-
  sensitive gates.

- src/shared/featureFlags.generated.test.ts: branch the disabled-
  experimental assertion on `process.env.CLEARPATH_E2E_EXPERIMENTAL`,
  since `pretest` regenerates the module with whatever env it sees.
  Running `CLEARPATH_E2E_EXPERIMENTAL=1 npm test` was previously a
  guaranteed failure; now the test mirrors the build behavior.
…verrides

Two follow-ups from the latest AI review pass:

- `FlagChangeEvent.key` was typed as `string`, even though the only
  emitter (`emitFlagChanges`) iterates `keyof FeatureFlags`. Narrow
  the type to `FeatureFlagKey` so subscribers get the same end-to-end
  type-safety the rest of the module exposes.

- `feature-flags:set` previously sanitized compiled-out experimental
  overrides by writing an explicit `false` into the store. That
  persists a per-user override that would silently override the new
  default if the flag later becomes compiled-in / default-on. Delete
  the key from the sanitized args instead so no override is written
  for compiled-out flags — `clampToCompiledIn` already keeps the
  runtime view consistent regardless of any legacy stored value.
* feat(feature-flags): add locked preview mode + FEATURES.md

CLEARPATH_FLAGS_LOCKED=1 produces a build where the runtime treats
features.json as the absolute source of truth: stored overrides are
ignored, the feature-flags:set/apply-preset/reset IPC handlers become
no-ops, and the Settings → Feature Flags page hides off-by-default
flags entirely. On-by-default flags render read-only with a 🔒 banner
explaining the build was locked.

This gives us three orthogonal modes (composable):

- default                              dev with full UI overrides
- CLEARPATH_FLAGS_LOCKED=1             "what an end user with no
                                         overrides sees, frozen to
                                         features.json"
- CLEARPATH_E2E_EXPERIMENTAL=1         every experimental flag forced
                                         on at generation time
                                         (previously e2e-only)

Plumbed through:
- generator emits BUILD_FLAGS_LOCKED constant
- electron.vite.config.ts adds a __FEATURES_LOCKED__ define
- main IPC short-circuits writes when locked
- FeatureFlagContext skips IPC overrides + bypasses the progressive
  preset when locked, exposes `locked: boolean` to consumers
- FeatureFlagSettings hides the locked-mode UI affordances + filters
  to only flags whose effective value is true

New scripts:
- npm run dev:preview          locked mode in dev
- npm run dev:experimental     experimentals forced on in dev
- npm run build:locked         / build:experimental
- npm run preview:locked       / preview:experimental

FEATURES.md documents the data model, both pipelines, all env vars,
the dev/preview workflow matrix, how to verify tree-shaking with grep,
and how to add or remove a flag.

Stacks on top of #52 (feature/experimental-feature-flags) since the
features.json system that this builds on hasn't merged to main yet.

Verified against the build matrix:
- default build: no PrScores/Backstage chunks in out/
- locked build: BUILD_FLAGS_LOCKED = true literal in out/main/index.js
- experimental build: PrScores (111KB) + BackstageExplorer chunks
  present, IPC channel strings appear in their respective chunks

* fix(extensions): hide nav links when their feature flag is off

Reported: clicking PR Scores in the sidebar lands on the Work tab.

Root cause: PR #52's `<Route path="pr-scores">` redirects to /work when
`__FEATURES__.showPrScores` is false (the redirect-arm exists so the
extension-pinned link to `#/pr-scores` doesn't unmount the Layout
mid-test). But the PR Scores extension's manifest never set
`featureGate` on its nav entry, so the sidebar always rendered the
link — clicking it advertised a feature that just bounced you away.

The Sidebar already filters extension nav items on `nav.featureGate`
(Sidebar.tsx:315). The fix is purely in the extension manifests:

- PR Scores:          featureGate: ["showPrScores"]
- Backstage Explorer: featureGate: ["showBackstageExplorer"]
- Efficiency Coach:   featureGate: ["showEfficiencyCoach"]

With this in place:
- npm run dev               — links hidden, no broken nav.
- npm run dev:experimental  — links shown, pages reachable.
- npm run dev:preview       — links hidden (locked + flag off).

The redirect-to-/work in App.tsx stays as defense-in-depth for
direct-URL navigation (bookmarks, deep links, etc.).

FEATURES.md updated with a "if your flag gates an extension-contributed
page" note pointing at this manifest field.

* feat(feature-flags): gate MCP Servers tab behind showMcpServers (experimental, off)

Adds a new build-time-gated experimental flag for the MCP integration
surface (Connect → MCP Servers tab + the McpCatalogGrid / McpRegistryList /
sync hooks beneath it).

- features.json: showMcpServers, experimental: true, enabled: false, 1.9.0
- Connect.tsx: lazy(import('../components/mcp/McpTab')) gated on
  __FEATURES__.showMcpServers so Rollup tree-shakes the MCP chunks
  out of default builds. The sub-tab is filtered out of the tablist
  via flags.showMcpServers; URL `?tab=mcp` falls back to integrations
  when the flag is off (keeps the legacy /connections redirect safe).
- FeatureFlagSettings.tsx: surfaced under "Experimental Features".

Verified build matrix:
- npm run build               → no McpTab-*.js chunk; renderer bundle
                                shrinks ~67 KB (4145 → 4078)
- npm run build:experimental  → McpTab-*.js chunk emitted (lazy)
- 21/21 tests pass after a clean regen

* feat(feature-flags): gate Extensions tab behind showExtensions (experimental, off)

Adds the ExtensionManager UI to the same build-time-gating pattern as
showMcpServers. Already-installed extensions continue to load and
render via the /ext/:id/* route — only the install / enable / manage
surface (Connect → Extensions) is gated.

- features.json: showExtensions, experimental: true, enabled: false, 1.9.0
- Connect.tsx: lazy(import('../components/extensions/ExtensionManager'))
  gated on __FEATURES__.showExtensions; tab filtered out of the tablist
  via flags.showExtensions; URL `?tab=extensions` falls back to
  integrations when gated off.
- FeatureFlagSettings.tsx: surfaced under "Experimental Features".

Verified build matrix:
- npm run build               → no ExtensionManager-*.js (or McpTab-*.js)
                                chunk; renderer bundle 4055 KB (down
                                from 4078 with just MCP gated, 4145
                                pre-gating)
- npm run build:experimental  → both ExtensionManager-*.js and
                                McpTab-*.js chunks emitted (lazy)
- 21/21 tests pass after a clean regen

* Update features.json
… crawl

The renderer code reads `__FEATURES__` (a Vite `define` constant) at
import time. Vitest uses its own vite config separate from
electron.vite.config.ts, so any test that transitively imported
Connect.tsx or App.tsx threw `__FEATURES__ is not defined` and
exploded before the first assertion. Configure.test.tsx was the
canonical victim.

Mirror the same env-aware loadFeatures logic from electron.vite.config.ts
in vitest.config.ts so renderer tests see identical flag substitution.
Both `__FEATURES__` and `__FEATURES_LOCKED__` are now defined in tests.

Verified: full suite (205 files / 3675 tests) passes.

Also extends the experimental e2e crawl to cover the two new flag-
gated Connect sub-tabs:
- experimental-features/connect--tab-mcp        (showMcpServers)
- experimental-features/connect--tab-extensions (showExtensions)

These weren't surfaced before because they live as ?tab= search params
on /connect rather than full routes. The spec helper sets
`window.location.hash = '#/connect?tab=mcp'`; HashRouter parses the
search half via useSearchParams and Connect.tsx's URL-fallback effect
honors the flag-gate. Marker text "Custom server" / "Permissions" is
unique to the McpTab / ExtensionManager bodies — guards against
silent fallback capture if the build is ever produced without
CLEARPATH_E2E_EXPERIMENTAL=1.

Note: PR #53's CI does not currently fire because it targets
feature/experimental-feature-flags (the stack base) rather than main.
That resolves automatically when #52 merges and #53 retargets.
Regular screenshot-regression failed because the crawl was still
clicking #connect-tab-extensions / #connect-tab-mcp, which are now
flag-gated and hidden in default builds. Drop them from CONNECT_TABS
(experimental crawl already covers them under
experimental-features/connect--tab-{mcp,extensions}). Also delete the
two stale baselines that were captured before the gating shipped.

Also addresses the four AI review comments from this round:

1. featureFlags.generated.ts had `BUILD_FLAGS.showCostTracking = true`
   from a CLEARPATH_E2E_EXPERIMENTAL=1 regen leaking forward into the
   committed file. Regenerated with a clean env.

2. FeatureFlagSettings.test.tsx's `ALL_ON: FeatureFlags = { ... }`
   literal was missing showMcpServers / showExtensions and would have
   silently broken on the next typed-fixture-using test. Refactor to
   derive from FEATURE_FLAG_KEYS so future flag adds are absorbed
   automatically.

3. Connect.tsx: when a user toggles showExtensions / showMcpServers
   off at runtime while the corresponding tab is active (only
   reachable when the chunk is compiled in but flipped off via UI),
   `tab` state lingered and the page rendered blank. Added a guard
   effect that bounces to integrations whenever the active tab
   becomes invisible.

4. features.json: revert `showCostTracking.experimental` true → false.
   The cost-tracking code is interleaved across many surfaces (turn
   metadata, badges, analytics) — it's not a tree-shakeable chunk, so
   `experimental: true` doesn't gain anything and only conflicts with
   its placement in the non-experimental "Session Features" group +
   progressive presets. Stays runtime-toggleable.

3675 tests pass.
The previous marker "Permissions" only appears on extension cards
that have been expanded — and the screenshot test never expands them,
so 3 passed but `captures experimental page: connect?tab=extensions`
timed out waiting for text that the page never renders in its
default state.

Switch to "Install Extension" — the always-visible button at the top
of the ExtensionManager body. The DOM dump confirms it renders
immediately for both the empty and populated states.
…CKED__

Three of the four review comments from this round.

1. featureFlagHandlers.ts: when an experimental flag's code gets
   tree-shaken in this build, `feature-flags:set` already drops *new*
   writes for that key — but any value that was persisted by a
   previous build (where the flag was compiled in) keeps living in
   the store. clampToCompiledIn keeps it inert at runtime today, but
   if the flag becomes compiled-in again later the stale `true` would
   silently re-enable the feature even when the new build's default
   is off. Add a one-time `scrubStaleCompiledOutOverrides()` at
   handler-registration time that purges those keys from the
   persisted store. Idempotent.

2. electron.vite.config.ts: `__FEATURES_LOCKED__` define had no
   consumers — locked-mode logic uses `BUILD_FLAGS_LOCKED` from the
   generated module instead. Drop the dead define + its `flagsLocked`
   computation so future readers don't go hunting for a second
   source-of-truth.

3. vitest.config.ts: same dead define in test config; removed.

Comment #4 (generator output is env-dependent → committed file
mutates) is being deferred — the fix would require migrating every
BUILD_FLAGS consumer to read the Vite `__FEATURES__` define instead,
which is a larger refactor we'd rather scope as a follow-up.

3675 tests pass.
Introduce the Work launchpad and active-session features with tests and routing tweaks. Adds ActiveSessionsBanner (mounted in Layout) with collapse persistence, session chips, status dot styling, and navigation to /work?id=<sessionId>. Implements ActiveSessionsCard, QuickStartCard (with advanced options persisted to localStorage), RecentSessionsCard, and related unit tests. Adds useActiveSessions hook and Composer support for deep-linking a workflowId to preload steps. Updates Sidebar to clear the /work?id= query when re-clicking Work and updates Work page/e2e tests to cover the new launchpad behavior and banner.
Promote Notes to a first-class peer of Sessions in the sidebar and overhaul
the new-chat Advanced section. Skills picker is per-session only — fixes
the "everything selected, can't deselect" bug where it mutated the global
skill registry. Note bodies never re-render in chat; the user bubble shows
compact agent / skill / note chips frozen at attach time.

Notes (top-level)
- New /notes page with three-pane layout: filters · cards · editor drawer
- Sidebar: Home · Sessions · Notes · Learn · Insights, gated on showNotes
- Removed Notes sub-tab under Sessions; ?tab=notes legacy URLs redirect
- 5-lesson "Capture context with Notes" path in /learn ties to feature unlock
- showNotes auto-unlocks at the `exploring` progressive-disclosure stage

Sessions Advanced redesign
- Stacked sections with search: Agent (single-select), Skills (multi-select),
  Notes (multi-select), then Permission mode + Additional directories
- Skills no longer call skills:toggle — per-session selection only
- Removed: Memories config-files picker, Templates dropdown, Attach files
  (kept out of scope; will return as dedicated features)

In-chat audit-trail chips
- User bubble shows pill chips: Agent / N skills / N notes
- Names/titles only; note body never reaches the rendered DOM
- Frozen on message metadata so chips survive note deletion + flag toggling

AI context framing
- notes:get-bundle-for-prompt emits a clearly framed block with preamble:
    <notes count="N">
      <note title="..." category="..." tags="..." source="...">{body}</note>
    </notes>
    User request: ...
- Titles are the cite handle; UUIDs never leak

Configure → Project Memory rename
- Old "Memory & Context" page renamed; Notes tab dropped (moved to /notes)
- "Starter Memories" tab renamed to "Templates"

Test infrastructure
- jsdom: configured http://localhost/ URL via environmentOptions
- Polyfilled window.localStorage / sessionStorage in setup-coverage.ts so
  jsdom's opaque-origin default doesn't break tests
- handlers.test cli:send-input updated for the new attachedNotes arg
- QuickStartCard.test rewritten for the button-based agent picker;
  dropped tests for removed features (templates/files/memories/skills:toggle);
  added coverage for the new skill + note local multi-select

Sessions launchpad polish (in-flight branch work)
- QuickStartCard: Provider/Transport selectors + advanced state in localStorage
- ActiveSessionsBanner: AA-contrast CLI badges, status labels
- AuthManager + CopilotAdapter touch-ups, expanded modelTiers
- Composer / SessionSettingsModal / NotificationInbox refinements

Version bump: 1.9.0 → 1.13.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
E2E screenshot crawl
- Sidebar nav helper still searched for the literal "Work" label after the
  Sessions rename, so the screenshot-crawl spec timed out before capturing
  the page. Updated all e2e specs (navigation, smoke, work-page,
  app-lifecycle, screenshot-crawl) to use 'Sessions'. Route is still /work,
  so screenshot baseline filenames stay `work--*` for backward compat.
- Added Notes as a top-level sidebar entry in SIDEBAR_PAGES (optional —
  gated on showNotes; the spec skips when not rendered).
- Trimmed WORK_TABS: dropped `wizard` and `memory` (notes moved to /notes,
  wizard surface was retired); only `session` / `compose` / `schedule`
  remain.

Notes page flicker
- Replaced `loading: boolean` with `initialized: boolean`. The "Loading
  notes…" message now only renders before the FIRST notes:list resolves.
  Subsequent refreshes (triggered by the editor drawer's debounced save
  via `onChange()`) no longer toggle a global flag, so the placeholder
  stops flashing over the user's typing.
- handleNew now optimistically inserts the created note into local state
  and only does a background refresh, instead of blocking on refresh()
  before the editor drawer opens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The chip metadata (attachedAgent, attachedSkills, attachedNotes) was
already being persisted on the user message in clear-path-sessions.json,
but the renderer's rehydration paths in Work.tsx dropped these fields
when mapping the persisted log back into OutputMessage. As a result,
leaving a session and coming back showed a bare bubble — the agent /
skill / note pills the user had selected at submit time disappeared.

Both rehydration paths now pull the fields through:
- Active sessions: cli:get-message-log return type extended; mapper
  copies attachedAgent / attachedSkills / attachedNotes onto each
  OutputMessage.
- Persisted sessions (cli:get-persisted-sessions): same.

The chips on the live bubble still come straight from React state at
send time, so the only behavior that changes is the recovered view —
it now matches what the user saw when they sent the message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implement bulk archive/unarchive support for persisted sessions and wire it through IPC, preload and the UI. CLIManager.archivePersistedSessions was added to toggle archived flags for multiple session IDs; ipc handlers and preload allowed channels were updated to expose 'cli:archive-sessions'. The SessionManager UI now surfaces a bulk Archive/Unarchive action and clears selection after the action, and corresponding unit tests were added for the renderer, CLI manager, and IPC handlers. An end-to-end spec (e2e/session-manager.spec.ts) was added to cover bulk operations, and developer docs (.claude/agents/core-developer.md) were expanded to mandate running tests and a Definition of Done. Also toggled BUILD_FLAGS_LOCKED to true in featureFlags.generated.ts.
zalos and others added 3 commits April 27, 2026 23:44
The bulk-archive commit baked BUILD_FLAGS_LOCKED=true into the committed
generated file because npm run build was invoked from a shell that had
CLEARPATH_FLAGS_LOCKED=1 exported. Regenerated the snapshot with the env
var cleared so normal dev/prod builds keep flag toggles interactive, as
the docstring promises. The generator script itself was already correct.

Addresses Copilot review feedback on PR #55.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add end-to-end coverage for the new My Work aggregation page: e2e/my-work.spec.ts and a baseline screenshot (my-work--initial.png). Wire the page into the app (src/renderer/src/pages/MyWork.tsx and test) and update related renderer components (App, Sidebar, FeatureFlagSettings) and feature flags (features.json, featureFlags.generated.ts). Adjust integration/ipc/preload bits (atlassian.ts, integrationHandlers.ts, preload/index.ts) as needed. Update the screenshot crawl to include My Work as an optional page and refresh many screenshot baselines (LFS OIDs updated) to reflect UI changes.
Copilot AI review requested due to automatic review settings April 28, 2026 05:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new My Work surface (flag-gated, on by default) that aggregates a user’s Jira + GitHub workload, alongside new IPC aggregation endpoints and expanded test coverage (unit + WDIO E2E), with updated visual regression baselines and strengthened contributor validation guidance.

Changes:

  • Introduces /my-work page + sidebar entry, behind showMyWork.
  • Adds aggregated integration IPC handlers (integration:jira-my-work, integration:github-my-work) and whitelists them in preload.
  • Adds bulk archive/unarchive sessions IPC + UI, new unit/E2E coverage, and updates screenshot baselines/docs.

Reviewed changes

Copilot reviewed 78 out of 78 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/shared/featureFlags.generated.ts Adds showMyWork key/type/meta/defaults to generated flags.
src/renderer/src/pages/MyWork.tsx Implements the My Work page UI, loading/empty states, and IPC fan-out.
src/renderer/src/pages/MyWork.test.tsx Unit tests for My Work rendering, disconnected/empty/error states, and refresh.
src/renderer/src/components/settings/FeatureFlagSettings.tsx Exposes showMyWork in feature flag settings UI.
src/renderer/src/components/Sidebar.tsx Adds sidebar nav item for /my-work behind showMyWork.
src/renderer/src/components/SessionManager.tsx Adds bulk archive/unarchive action invoking new IPC.
src/renderer/src/components/SessionManager.test.tsx Unit tests for bulk archive/unarchive UI + IPC invocation behavior.
src/renderer/src/App.tsx Registers the /my-work route.
src/preload/index.ts Whitelists new IPC channels (cli:archive-sessions, integration:*my-work).
src/main/ipc/integrationHandlers.ts Adds integration:github-my-work aggregated GitHub search handler.
src/main/ipc/handlers.ts Adds cli:archive-sessions IPC handler wiring.
src/main/ipc/handlers.test.ts Extends IPC handler tests to include cli:archive-sessions.
src/main/integrations/atlassian.ts Adds Jira /search/jql helper, updates jira-search, and adds integration:jira-my-work.
src/main/cli/CLIManager.ts Adds archivePersistedSessions bulk archive implementation.
src/main/cli/CLIManager.test.ts Tests for bulk archive/unarchive behavior in the session store.
features.json Declares the showMyWork feature flag metadata.
e2e/session-manager.spec.ts WDIO E2E coverage for bulk archive/unarchive in SessionManager modal.
e2e/screenshot-crawl.spec.ts Adds My Work to screenshot crawl (optional, disconnected baseline).
e2e/my-work.spec.ts WDIO E2E coverage for My Work sidebar navigation + disconnected CTAs.
.claude/agents/core-developer.md Strengthens validation workflow + definition-of-done checklist.
e2e/screenshots/baseline/work--tab-session.png Updates visual baseline.
e2e/screenshots/baseline/work--tab-schedule.png Updates visual baseline.
e2e/screenshots/baseline/work--tab-compose.png Updates visual baseline.
e2e/screenshots/baseline/work--initial.png Updates visual baseline.
e2e/screenshots/baseline/notes--initial.png Updates visual baseline.
e2e/screenshots/baseline/my-work--initial.png Adds new visual baseline for My Work.
e2e/screenshots/baseline/learn--initial.png Updates visual baseline.
e2e/screenshots/baseline/insights--tab-pr-health.png Updates visual baseline.
e2e/screenshots/baseline/insights--tab-efficiency.png Updates visual baseline.
e2e/screenshots/baseline/insights--tab-compliance.png Updates visual baseline.
e2e/screenshots/baseline/insights--tab-catalog-insights.png Updates visual baseline.
e2e/screenshots/baseline/insights--tab-activity.png Updates visual baseline.
e2e/screenshots/baseline/insights--initial.png Updates visual baseline.
e2e/screenshots/baseline/home--initial.png Updates visual baseline.
e2e/screenshots/baseline/connect--tab-webhooks.png Updates visual baseline.
e2e/screenshots/baseline/connect--tab-plugins.png Updates visual baseline.
e2e/screenshots/baseline/connect--tab-integrations.png Updates visual baseline.
e2e/screenshots/baseline/connect--tab-environment.png Updates visual baseline.
e2e/screenshots/baseline/connect--initial.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-workspaces.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-workspaces--sub-settings.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-workspaces--sub-repos.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-workspaces--sub-broadcast.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-workspaces--sub-activity.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-wizard.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-tools.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-team.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-team--sub-wizard.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-team--sub-sync.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-team--sub-marketplace.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-team--sub-activity.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-skills.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-setup.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-settings.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-settings--sub-profiles.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-settings--sub-notifications.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-settings--sub-model.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-settings--sub-limits.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-settings--sub-features.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-settings--sub-data.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-scheduler.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-policies.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-policies--sub-violations.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-policies--sub-editor.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-memory.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-memory--sub-instructions.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-memory--sub-context.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-memory--sub-config-files.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-memory--sub-cli-memory.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-branding.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-branding--sub-ui-colors.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-branding--sub-surfaces.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-branding--sub-preview.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-branding--sub-identity.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-branding--sub-colors.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-agents.png Updates visual baseline.
e2e/screenshots/baseline/configure--tab-accessibility.png Updates visual baseline.
e2e/screenshots/baseline/configure--initial.png Updates visual baseline.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +297 to +307
const { response, error } = await authenticatedFetch(url)
if (error) return { issues: [], error }
if (!response || !response.ok) {
const status = response?.status ?? 0
let body = ''
try { body = (await response?.text()) ?? '' } catch { /* ignore */ }
return { issues: [], error: `search/jql HTTP ${status}: ${body.slice(0, 200)}` }
}

const data = (await response.json()) as {
issues?: Array<{
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

searchJiraIssuesViaJql can throw (e.g., network failure from authenticatedFetch, invalid JSON from response.json()), which will bubble up and reject IPC handlers like integration:jira-search/integration:jira-my-work. Please wrap the fetch + JSON parsing in a try/catch and return { issues: [], error: ... } on exceptions so renderer callers always get a structured error response.

Copilot uses AI. Check for mistakes.
Comment on lines 498 to 503
ipcMain.handle(
'integration:jira-search',
async (
_e,
args: { jql: string; startAt?: number; maxResults?: number; fields?: string[] },
) => {
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

integration:jira-search still accepts startAt in its args type, but it is no longer used (the new /search/jql call only uses maxResults). This is misleading for callers and suggests pagination still works. Consider removing startAt from the handler signature (and any callsites/types) or explicitly documenting/validating that it is ignored.

Copilot uses AI. Check for mistakes.
Comment on lines +1178 to +1196
const boardsUrl = `${meta.siteUrl}/rest/agile/1.0/board?maxResults=50`
const { response: boardsResp } = await authenticatedFetch(boardsUrl)
if (!boardsResp || !boardsResp.ok) {
// Agile API may be unavailable (Jira Software disabled) — non-fatal
result.sprintError = boardsResp ? `Boards API HTTP ${boardsResp.status}` : 'Boards API unreachable'
log.warn('[atlassian] jira-my-work: %s', result.sprintError)
} else {
const boardsData = (await boardsResp.json()) as {
values?: Array<{ id?: number; name?: string; type?: string }>
}
const candidateBoards = (boardsData.values ?? []).filter((b) => typeof b.id === 'number')

for (const board of candidateBoards) {
if (!board.id) continue
const sprintUrl = `${meta.siteUrl}/rest/agile/1.0/board/${board.id}/sprint?state=active`
const { response: sprintResp } = await authenticatedFetch(sprintUrl)
// Boards that don't support sprints (kanban without backlog) return
// 400; we just skip them and keep scanning.
if (!sprintResp || !sprintResp.ok) continue
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

The Jira sprint scan does up to 1 + N sequential Agile API requests (boards list, then one sprint request per board until a hit). With maxResults=50, worst-case is 50+ HTTP calls per refresh, which can be slow and may trip Atlassian rate limits. Consider bounding the scan (e.g., first N boards), caching the last successful boardId, and/or parallelizing a limited number of sprint checks with a small concurrency cap.

Copilot uses AI. Check for mistakes.
Comment on lines +1225 to +1229
// Pull this sprint's issues (capped to 50; the sprint shouldn't be huge)
const issuesUrl = `${meta.siteUrl}/rest/agile/1.0/sprint/${active.id}/issue?maxResults=50`
const { response: issuesResp } = await authenticatedFetch(issuesUrl)
if (!issuesResp || !issuesResp.ok) break

Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

If fetching sprint issues fails (issuesResp missing or non-OK), the handler breaks out without setting sprintError, but it may already have set activeSprint. This can make the UI show an apparently valid sprint with an empty issues list ("No issues…") instead of an error. Please set result.sprintError (e.g., HTTP status / unreachable) before breaking so the renderer can render an accurate failure state.

Copilot uses AI. Check for mistakes.
Comment on lines +494 to +505
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
{/* ── Sprint card (Jira) ───────────────────────────────────────── */}
{jiraConnected ? (
<SprintCard
sprint={jira?.activeSprint ?? null}
issues={jira?.sprintIssues ?? []}
siteUrl={status?.atlassian?.siteUrl ?? ''}
error={jira?.sprintError ?? null}
/>
) : (
<DisconnectedCard service="jira" onConnect={() => navigate('/connect?tab=integrations')} />
)}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

During the initial load, status is set before the Jira/GitHub data resolves, so jiraConnected/githubConnected can flip to true while jira/github are still null. In that window the cards render “No active sprint.” / other empty states even though data is still loading (and the separate loading shell is also shown). Consider gating the card bodies on loading (or on jira/github being non-null) and rendering per-card skeletons/spinners to avoid misleading empty-state flicker.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 28, 2026 05:38
@zalos zalos review requested due to automatic review settings April 28, 2026 05:38
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.

3 participants