docs(v10): self-contained worktree briefs for Phase 1-4

Splits the v10 Phase 1-4 work into 5 self-contained briefs in
.v10-worktrees/, one per worktree. Each brief is meant to be handed
to a single agent or developer:

  worktree-a-banners.md   — Phase 1 deprecation banners (parallel with B)
  worktree-b-archiver.md  — Phase 1 Archiver pure functions (parallel with A)
  worktree-c-readonly.md  — Phase 2 read-only mode + 10.0.0-beta.1 cut
  worktree-d-cliff.md     — Phase 3 the cliff (5 sub-PRs: D.2 → D.1 → D.3 → D.4 → D.5)
  worktree-e-cleanup.md   — Phase 4 cleanup + calendar-sync v0.1.0 release

Each brief includes:
- Setup commands (git worktree add, branch off, npm install)
- Files to modify and files to create
- Numbered task list with calendar estimates
- Definition of Done (per worktree)
- Conflicts to watch (cross-worktree)
- Open questions affecting THIS worktree (subset of the 5 main-plan questions)
- "Don't do these things" guardrails

The 5 main-plan open questions have been resolved with sensible defaults
(documented in .v10-worktrees/README.md). User can override before any
worktree commits to a particular answer; most decisions don't kick in until
Worktree D, giving a 3-4 week runway.

Phase 0 deferred items (TaskView.ts deletion, settings-migration.ts fold)
are explicitly assigned to Worktree D sub-PRs (D.4 and D.2 respectively),
with full resolution paths in PHASE0_DEFERRED.md.

WORKTREE_PLAN.md updated with a header pointer to the new briefs and
preserved as the umbrella overview.
This commit is contained in:
Quorafind 2026-04-07 10:44:03 +08:00
parent e90fc53706
commit d5d03cfd6b
7 changed files with 1626 additions and 0 deletions

88
.v10-worktrees/README.md Normal file
View file

@ -0,0 +1,88 @@
# Task Genius v10 — Worktree Briefs
This directory holds **self-contained briefs** for the 5 worktrees that
execute v10 Phase 1-4. Each brief is meant to be handed to a single agent
(or developer) who will create a worktree, do the work, and merge.
The full v10 plan lives at `~/.claude/plans/dynamic-mixing-pie.md` (in the
user's Claude config). Each brief here references the main plan but is
written to be readable on its own — an agent picking up brief D should not
need to read the entire v10 plan to start working.
## The 5 worktrees
| # | Brief | Phase | Branch | Calendar | Depends on |
|---|---|---|---|---|---|
| **A** | [worktree-a-banners.md](./worktree-a-banners.md) | 1 (9.14.0) | `refactor/v10-phase1-banners` | ~1 wk | Phase 0 (✅ done) |
| **B** | [worktree-b-archiver.md](./worktree-b-archiver.md) | 1 (9.14.0) | `refactor/v10-phase1-archiver` | ~1 wk | Phase 0 (✅ done) |
| **C** | [worktree-c-readonly.md](./worktree-c-readonly.md) | 2 (9.15.0) | `refactor/v10-phase2-readonly` | ~1 wk | A merged to master |
| **D** | [worktree-d-cliff.md](./worktree-d-cliff.md) | 3 (10.0.0) | `refactor/v10-phase3-cliff` | ~1 wk | C merged to master |
| **E** | [worktree-e-cleanup.md](./worktree-e-cleanup.md) | 4 (10.0.1) | `refactor/v10-phase4-cleanup` | ~1 wk | D merged to master |
## How to spawn agents
**Parallel start (recommended):** spawn agents A and B simultaneously, both
branched off `refactor/v10-phase0`. They modify disjoint files. Once A merges
to master, spawn C. Then D after C, then E after D.
**Solo execution:** the single-developer path is `A → B → C → D.2 → D.1 → D.3
→ D.4 → D.5 → E`. The worktree split is informational; you can use one working
tree if you prefer.
## Phase 0 status (the ground each worktree stands on)
Phase 0 is **complete** on `refactor/v10-phase0`. 6 commits, 71 new passing
tests, 10 of 12 DoD items checked. The 2 deferred items (delete TaskView.ts,
fold settings-migration.ts into the registry) are documented in
[../PHASE0_DEFERRED.md](../PHASE0_DEFERRED.md) and explicitly assigned to
worktree D sub-PRs (D.4 and D.2 respectively).
Phase 0 commits on `refactor/v10-phase0`:
- `dd3fe89a` — W0 test infrastructure (buildOrchestrator + InMemoryStorage + localforage mock)
- `1de55f4c` — W2/W2-bis/W3 (onunload race fixed, rebuild last-resort, worker timeouts)
- `fe4c976f` — W4 (typed cache scope map + invariants checker + LocalStorageCache version fix)
- `12481c17` — W1 (MigrationRegistry with version-keyed tombstones)
- `d583e967` — W5 (critical-path integration tests: roundtrip / settingsChange / cache invariants sequence)
- `e90fc537` — Phase 0 docs (PHASE0_DEFERRED.md, WORKTREE_PLAN.md)
## Decisions resolved (defaults — user can override before any worktree commits)
The 5 open questions from the main plan have been answered with sensible
defaults so worktrees can start without waiting on more decisions:
1. **Deprecation list:** ship exactly the 14 features listed in the main plan
(Workflow, Habit, Reward, Timer, Gantt, Quadrant, Timeline Sidebar,
File-as-Task, Holiday detector, Electron quick-capture, 4 quick-capture
modal variants, Working-On view, Flagged view, Habit view). Pull-back can
happen at sub-PR review time.
2. **Sub-plugin commitment:** YES, 4 new repos
(`task-genius-workflow`, `task-genius-habits`, `task-genius-timer`,
`task-genius-calendar-sync`). Each is small (~3-6k LOC). They share the
same tooling as the main plugin; the boundary is "share vault files,
nothing else" (no DataflowOrchestrator coupling).
3. **Default hotkeys:** SHIP the 5 proposed bindings
(`Mod+Shift+T` capture, `Mod+Shift+G` open view, `Mod+Shift+I` inbox,
`Mod+Shift+F` forecast, `Mod+Alt+P` priority, `Mod+Enter` mark done on
task line). Document in CHANGELOG that v10 sets defaults for the first
time. Users can opt out via Obsidian's hotkey settings.
4. **Cliff version:** 9.14 → 9.15 → 10.0 over 4 weeks (the original
proposal). Faster is better; the deprecation period exists to give beta
testers time, not as a UX runway.
5. **Beta tester recruitment:** pin a GitHub Discussion explicitly recruiting
testers. Don't depend on a pre-existing pool. The Discussion goes up the
day Worktree A starts; recruitment runs in parallel with banner work.
If the user wants to override any of these, they can do so before the
relevant worktree commits to a particular answer — most decisions come into
play in Worktree D (Phase 3), so the runway is ~3-4 weeks.
## See also
- [`../PHASE0_DEFERRED.md`](../PHASE0_DEFERRED.md) — the 2 W6 items deferred from Phase 0
- [`../WORKTREE_PLAN.md`](../WORKTREE_PLAN.md) — the original umbrella worktree plan (this directory subsumes it; WORKTREE_PLAN.md kept for reference)
- [`../PLAN.md`](../PLAN.md) — the original v10 widgets requirements doc (predates this refactor effort)
- `~/.claude/plans/dynamic-mixing-pie.md` — the full v10 refactor plan (in the user's Claude config)

View file

@ -0,0 +1,234 @@
# Worktree A — Phase 1 Deprecation Banners (9.14.0)
## What you're doing
Task Genius is being slimmed down for v10 (~95k LOC → ~40k LOC). Phase 1 is
the **announce + soft warning** week: deprecated settings tabs get a yellow
banner, the changelog modal auto-opens once on first launch of 9.14.0, and
nothing actually breaks for users yet. This is the gentle on-ramp.
Your worktree is one of two Phase 1 worktrees that run **in parallel**:
- **Worktree A (this one)** — UI banners + changelog modal (user-facing)
- **Worktree B** — Archiver pure functions (infrastructure for Phase 3)
A and B touch disjoint files. Both branch off `refactor/v10-phase0`.
## Phase 0 ground
You're branching off `refactor/v10-phase0` (already merged-quality, 6 commits,
71 new tests). Phase 0 added the MigrationRegistry, fixed lifecycle hazards,
added cache guardrails, and shipped integration test infrastructure. None of
that affects your work directly — you're touching settings UI files, not
dataflow.
## Setup
```bash
# From the main repo root
git fetch
git worktree add ../tg-phase1-banners refactor/v10-phase0
cd ../tg-phase1-banners
git checkout -b refactor/v10-phase1-banners
# Verify the integration tests pass on this branch
npm install
npm test -- --testPathPattern="integration/"
# Expect: 70+ passing in the integration namespace
```
## Files you'll create
- `src/components/features/settings/components/DeprecationBanner.ts` — new component (~60 LOC)
- `src/common/deprecation-messages.ts` — centralized i18n strings (~80 LOC)
## Files you'll modify (14 deprecated tabs + supporting files)
The 14 deprecated tabs each get a one-line `DeprecationBanner` injection at
the top of their `renderXxxSettingsTab()` function:
1. `src/components/features/settings/tabs/WorkflowSettingsTab.ts` → "Workflows move to `task-genius-workflow` in v10"
2. `src/components/features/settings/tabs/HabitSettingsTab.ts` → "Habits move to `task-genius-habits`"
3. `src/components/features/settings/tabs/RewardSettingsTab.ts` → "Rewards move to `task-genius-habits`"
4. `src/components/features/settings/tabs/TaskTimerSettingsTab.ts` → "Timer moves to `task-genius-timer`"
5. `src/components/features/settings/tabs/TimelineSidebarSettingsTab.ts` → "Timeline Sidebar removed in v10. Forecast view replaces it."
6. `src/components/features/settings/tabs/IcsSettingsTab.ts` (OAuth/CalDAV section only — read-only ICS stays in main plugin) → "OAuth/CalDAV moves to `task-genius-calendar-sync`"
7. `src/components/features/settings/tabs/IndexSettingsTab.ts` (file-as-task source section) → "File-as-Task source removed in v10"
8. `src/components/features/settings/tabs/DesktopIntegrationSettingsTab.ts` (electron-quick-capture window section) → "Electron quick-capture window removed in v10"
9-12. Sections within the views tab: Gantt, Quadrant, Habit view, Working-On view, Flagged view (5 view-config entries deprecated)
13. Holiday detector toggle (find via `grep -r "holiday-detector" src/components/features/settings/`)
14. AboutSettingsTab — gets a "Deprecations" collapsible at the top
Plus:
- `src/components/features/settings/index.ts` — export the new banner component
- `src/managers/changelog-manager.ts` — wire to auto-open the v10 announcement on 9.14.0 first launch
- `src/translations/locale/en.ts` — add new strings for banner copy
## Tasks (in order)
### Task 1 — Build the DeprecationBanner component (~½ day)
`src/components/features/settings/components/DeprecationBanner.ts`:
```ts
export interface DeprecationBannerProps {
tabId: string;
replacementText: string; // e.g. "Workflows move to task-genius-workflow"
exportLabel?: string; // e.g. "Export this section now"
exportAction?: () => Promise<void> | void;
learnMoreUrl?: string;
// Phase 2 will add: readOnly?: boolean
}
export function renderDeprecationBanner(
containerEl: HTMLElement,
props: DeprecationBannerProps,
): void {
// Yellow background, alert icon, message text, optional Export button,
// optional "Learn more" link. ~60 LOC. CSS class .tg-deprecation-banner.
}
```
Add CSS in `src/styles/setting.scss` (or wherever the existing settings styles
live — grep `.task-genius-setting`).
### Task 2 — Centralize the strings (~½ day)
`src/common/deprecation-messages.ts`:
```ts
export const DEPRECATION_MESSAGES = {
workflow: {
bannerText: "Workflows move to the task-genius-workflow plugin in v10. ...",
exportLabel: "Export workflows now",
learnMoreUrl: "https://github.com/.../discussions/...",
},
habit: { ... },
// ... 14 total entries
} as const;
```
This becomes the single source of truth so locales translate one set of
strings, not 14 spread across tabs. Phase 2 will add more keys here.
### Task 3 — Wire banners into 14 tabs (~1.5 days)
For each of the 14 tab files, add a one-line `renderDeprecationBanner(containerEl, ...)`
call at the top of the render function (before any existing content). The
tab continues to function normally; the banner is purely additive.
For tabs that have a deprecated **section** (not the whole tab) — IcsSettingsTab,
IndexSettingsTab, DesktopIntegrationSettingsTab — render the banner above the
deprecated section, not the whole tab.
The "Export this section now" button calls a stub from Worktree B's Archiver:
```ts
import { archiveWorkflows } from "@/migration/v10/Archiver"; // from Worktree B
exportAction: async () => {
const result = await archiveWorkflows(plugin);
// For Phase 1 we just write to vault — Phase 3 wraps in transactional migration
for (const [path, content] of result.files) {
await plugin.app.vault.create(path, content);
}
new Notice(`Archived ${result.files.size} files to Task Genius Archive/`);
},
```
**Coordination with Worktree B:** if B hasn't merged yet, stub the import as
`async () => { new Notice("Export coming soon"); }` and add a TODO. Do not
block on B.
### Task 4 — AboutSettingsTab "Deprecations" panel (~½ day)
`src/components/features/settings/tabs/AboutSettingsTab.ts` — add a collapsible
section at the top of the tab listing all 14 deprecations with one global
"Export everything" button. The button calls all 7 archive functions in
sequence (or sequentially with a progress notice if you want to be fancy).
### Task 5 — Auto-open v10 announcement on 9.14.0 first launch (~½ day)
`src/managers/changelog-manager.ts` already auto-opens on version change.
Verify the existing logic and add a one-time announcement modal for 9.14.0
that links to the pinned GitHub Discussion. Body content goes in
`src/translations/locale/en.ts` (a new key like `v10AnnouncementBody`).
### Task 6 — Update CHANGELOG.md and create the GitHub Discussion (~½ day, out of repo)
- Add a `## 9.14.0` section to CHANGELOG.md announcing the v10 deprecations
- Create a pinned GitHub Discussion titled **"Task Genius v10: what's changing
and how to prepare"** with the TL;DR table from the main plan, the archive
folder explanation, sub-plugin install instructions, and an FAQ section
- Pin a thread for beta tester recruitment
## Definition of Done
| # | Criterion | How to verify |
|---|---|---|
| 1 | All 14 deprecated tabs/sections show banners in real-vault smoke test | Manual: open Settings, visit each, confirm banner is at the top |
| 2 | Each banner's "Export this section" button creates files in `<vault>/Task Genius Archive/<section>/` | Manual: click each export button, verify the folder appears |
| 3 | Changelog modal auto-opens once on 9.14.0 first launch | Manual: bump manifest.json to 9.14.0, reload plugin, verify modal opens; reload again, verify it does NOT open |
| 4 | AboutSettingsTab has a "Deprecations" collapsible at the top with a global "Export everything" button | Manual |
| 5 | No regression in existing tab rendering (all 25 tabs still render their existing content) | Manual + `npm test` |
| 6 | New strings only in `en.ts`; locale fallback handles other languages | Code review |
| 7 | TypeScript build clean (`npm run build`) | Auto |
| 8 | Phase 0 integration tests still pass | `npm test -- --testPathPattern="integration/"` |
| 9 | CHANGELOG.md updated, GitHub Discussion pinned | Manual |
## Conflicts to watch
- **Worktree C (Phase 2)** will modify the same 14 tab files to add read-only
mode. **C cannot start until A is merged to master.** Don't try to run them
in parallel.
- **Worktree D.1 (Phase 3)** will DELETE most of these tabs. The banners get
removed in Phase 3. That's expected — your work is the bridge that gives
users 4 weeks of warning before the deletion.
## Open questions that affect THIS worktree
The v10 plan has 5 open questions; defaults are documented in
[`./README.md`](./README.md). The two that touch your work:
- **Q1 — Deprecation list:** the 14 features listed above are the default. If
the user pulls any back, your task list shrinks accordingly. Wait for
user confirmation if you're about to ship a banner for a feature they
changed their mind about.
- **Q5 — Beta tester recruitment:** the GitHub Discussion goes up when your
worktree starts. If the user doesn't have a tester pool, the Discussion
recruits explicitly.
The other 3 (sub-plugin commitment, hotkey policy, cliff version) don't
affect this worktree — they kick in for Worktree C and D.
## Useful existing utilities to reuse
- `src/managers/changelog-manager.ts` — already supports auto-open on version change (Task 5)
- `src/components/features/changelog/` — ChangelogView component (re-use for the announcement)
- `src/translations/helper.ts``t()` translation helper
- The existing `Setting()` chain pattern from any tab file is your model for adding the banner above existing settings
## Don't do these things
- **Don't delete any tabs.** Phase 3 owns deletions. Banners are additive only.
- **Don't make any tab read-only.** That's Phase 2 / Worktree C.
- **Don't wire the banners' export action through MigrationRegistry.** That's
Phase 3 / Worktree D.5. For Phase 1, the export is a direct call to the
Archiver function (which Worktree B is building in parallel).
- **Don't touch the dataflow layer.** Your changes are 100% UI.
- **Don't translate strings to non-English locales.** Just `en.ts`. The locale
prune script in Phase 4 (Worktree E) handles parity.
- **Don't migrate any callers off `Orchestrator.onSettingsChange(scopes[])` to
the typed `onSettingsFieldsChanged(fields[])`.** Leave that for Phase 1+
feature work as features are touched. Your worktree shouldn't even import
the Orchestrator.
## When you're done
```bash
# From your worktree
git push origin refactor/v10-phase1-banners
gh pr create --base master --title "feat(settings): deprecation banners (Phase 1, Worktree A)"
```
After merge, signal that **Worktree C is unblocked** (it depends on A's
merge to master).

View file

@ -0,0 +1,291 @@
# Worktree B — Phase 1 Archiver Pure Functions (9.14.0)
## What you're doing
Task Genius is being slimmed down for v10. When users upgrade to 10.0.0,
their persistent data for deprecated features (workflows, habits, rewards,
etc.) gets archived to a vault folder so they don't lose it. The archive
folder is also where extracted sub-plugins look for "previous data" on first
install.
Your job is to build the **pure functions** that produce the archive — the
serialization logic — without any wiring to the UI or the migration system.
That wiring is Phase 3 / Worktree D.5.
You're running **in parallel with Worktree A** (banners). A modifies UI files,
B modifies new infrastructure files. Zero conflict.
## Phase 0 ground
You're branching off `refactor/v10-phase0` (already merged-quality, 6 commits).
Phase 0 added the MigrationRegistry which Phase 3 will use to wrap your
archiver functions in atomic migration steps. You don't need to touch the
registry yourself.
## Setup
```bash
git fetch
git worktree add ../tg-phase1-archiver refactor/v10-phase0
cd ../tg-phase1-archiver
git checkout -b refactor/v10-phase1-archiver
npm install
npm test -- --testPathPattern="integration/"
# Expect: 70+ passing
```
## Files you'll create
- `src/migration/v10/Archiver.ts` — 7 pure archive functions (~300 LOC)
- `src/migration/v10/index.ts` — barrel export
- `src/migration/v10/types.ts` — shared types (`ArchiveSection`, etc.)
- `src/__tests__/migration/v10/Archiver.test.ts` — fixture-based tests (~250 LOC)
- `src/__tests__/migration/v10/__fixtures__/realistic-settings.ts` — sample settings shapes for the 7 sections
## Files you will NOT touch
- `src/index.ts` (Worktree D wires the archiver into onload)
- `src/utils/migration/MigrationRegistry.ts` (Worktree D adds the v10-archive step)
- Any settings tab files (Worktree A injects the export buttons that *call* your functions)
- The DataflowOrchestrator (no dataflow concerns here)
## Tasks (in order)
### Task 1 — Define the contract (~½ day)
`src/migration/v10/types.ts`:
```ts
/**
* The result of one archive function. Pure data — no I/O happens inside the
* function. The caller is responsible for actually writing files via vault.
*/
export interface ArchiveSection {
/** Section name, e.g. "workflows", "habits", "timer". */
section: string;
/** Files to write, keyed by relative path under <vault>/Task Genius Archive/ */
files: Map<string, string>;
/** Human-readable summary for the migration confirmation modal. */
summary: string;
/** Optional structured count for the modal: {workflows: 47, habits: 12} */
itemCount: number;
}
export interface ArchiveManifest {
version: string; // plugin version that produced this archive
exportedAt: number; // unix epoch ms
sections: Array<{
section: string;
itemCount: number;
files: string[]; // relative paths
}>;
}
```
### Task 2 — Implement the 7 archive functions (~3-4 days)
`src/migration/v10/Archiver.ts`:
```ts
import type TaskProgressBarPlugin from "@/index";
import type { ArchiveSection } from "./types";
export async function archiveWorkflows(
plugin: TaskProgressBarPlugin,
): Promise<ArchiveSection> {
const workflows = plugin.settings.workflow?.definitions ?? [];
const files = new Map<string, string>();
// workflows.json — raw shape, round-trippable
files.set("workflows/workflows.json", JSON.stringify(
{ version: "v10-archive-1", definitions: workflows },
null,
2,
));
// workflows.md — human-readable summary, one workflow per H2
const md = renderWorkflowsMarkdown(workflows);
files.set("workflows/workflows.md", md);
return {
section: "workflows",
files,
summary: `${workflows.length} workflow definition${workflows.length === 1 ? "" : "s"}`,
itemCount: workflows.length,
};
}
// ... 6 more functions
```
The 7 functions to implement (each takes `plugin` and returns an `ArchiveSection`):
1. **`archiveWorkflows`** — reads `plugin.settings.workflow.definitions`. Output: `workflows/workflows.json` + `workflows/workflows.md`.
2. **`archiveHabits`** — reads `plugin.settings.habit.habits` (array). Output: `habits/habits.json` + `habits/habits.md`. Each habit gets a markdown section with type, schedule, and history if present.
3. **`archiveRewards`** — reads `plugin.settings.rewards.rewardItems` and `occurrenceLevels`. Output: `rewards/rewards.json` + `rewards/rewards.md`.
4. **`archiveTimerData`** — reads `plugin.settings.taskTimer` config + any in-memory `timerManager` state if accessible. Output: `timer/timer-data.json` + `timer/timer-summary.md`. Note: timer state may also live in localStorage; check `src/services/timer-export-service.ts` for the existing export shape and reuse it.
5. **`archiveCalDavSources`** — reads `plugin.settings.icsIntegration.sources` AND OAuth provider configs. **CRITICAL: strip OAuth tokens** before serializing. Output: `calendar-sync/caldav-sources.json` (URLs + names only).
6. **`archiveRemovedViews`** — reads `plugin.settings.viewConfiguration[]` and filters to entries whose ID is in `["gantt", "quadrant", "habit", "working-on", "flagged"]`. Output: `views/removed-views.json` (per-view filterRules + sort + visibility).
7. **`archiveOrphans`** — catch-all: reads any settings keys that don't have a dedicated archiver and aren't in the v10 keep-list. Output: `orphan-settings.json`. This is the safety net for "we deprecated something we forgot to write a function for."
Plus a top-level orchestrator:
```ts
export async function archiveAll(
plugin: TaskProgressBarPlugin,
): Promise<{ sections: ArchiveSection[]; manifest: ArchiveManifest }> {
const sections = await Promise.all([
archiveWorkflows(plugin),
archiveHabits(plugin),
archiveRewards(plugin),
archiveTimerData(plugin),
archiveCalDavSources(plugin),
archiveRemovedViews(plugin),
archiveOrphans(plugin),
]);
const manifest: ArchiveManifest = {
version: plugin.manifest.version,
exportedAt: Date.now(),
sections: sections.map((s) => ({
section: s.section,
itemCount: s.itemCount,
files: [...s.files.keys()],
})),
};
return { sections, manifest };
}
```
### Task 3 — Build realistic test fixtures (~½ day)
`src/__tests__/migration/v10/__fixtures__/realistic-settings.ts`:
```ts
export const settingsWith47Workflows: Partial<TaskProgressBarSettings> = {
workflow: {
enableWorkflow: true,
definitions: [/* 47 realistic workflow shapes */],
timestampFormat: "YYYY-MM-DD HH:mm",
autoAddTimestamp: true,
calculateSpentTime: false,
},
};
export const settingsWith12Habits: Partial<TaskProgressBarSettings> = {
habit: {
enableHabits: true,
habits: [/* 12 mixed daily/count/scheduled/mapping habits */],
},
};
// ... fixtures for the other 5 sections
```
These fixtures double as documentation for what realistic v9 user data looks
like. Phase 3's migration confirmation modal will use the same shapes for its
"X workflows, Y habits" copy.
### Task 4 — Test fixtures end-to-end (~1 day)
`src/__tests__/migration/v10/Archiver.test.ts`:
```ts
import { archiveWorkflows, archiveAll } from "@/migration/v10/Archiver";
import { settingsWith47Workflows, ... } from "./__fixtures__/realistic-settings";
describe("Archiver (Phase 1 Worktree B)", () => {
it("archiveWorkflows produces expected files for 47 workflows", async () => {
const plugin = makeFakePlugin({ settings: settingsWith47Workflows });
const result = await archiveWorkflows(plugin);
expect(result.itemCount).toBe(47);
expect(result.files.has("workflows/workflows.json")).toBe(true);
expect(result.files.has("workflows/workflows.md")).toBe(true);
const json = JSON.parse(result.files.get("workflows/workflows.json")!);
expect(json.definitions).toHaveLength(47);
});
// ... 1 test per section + 1 for archiveAll
});
```
`makeFakePlugin` is a tiny helper — just `{ settings, manifest: {version: "9.14.0"} }`.
You don't need a full plugin instance.
## Definition of Done
| # | Criterion | How to verify |
|---|---|---|
| 1 | All 7 archive functions return correct shapes for known inputs | `npm test -- --testPathPattern="migration/v10/Archiver"` |
| 2 | `archiveAll` orchestrator produces a valid manifest | Test asserts manifest shape |
| 3 | OAuth tokens are stripped from `archiveCalDavSources` output | Test asserts no `accessToken`/`refreshToken` keys in JSON |
| 4 | Each section has both a JSON (round-trippable) and markdown (human) representation, where applicable | Test asserts both files exist |
| 5 | Functions are pure: no `vault.create`, no `localforage` calls, no `app.workspace.trigger` | Code review |
| 6 | TypeScript build clean (`npm run build`) | Auto |
| 7 | Phase 0 integration tests still pass | `npm test -- --testPathPattern="integration/"` |
| 8 | NOT wired into MigrationRegistry yet | Code review (no edits to `src/utils/migration/`) |
## Coordination with Worktree A
Worktree A's banner export buttons need to call your functions. Provide a
clean import surface:
```ts
// from src/migration/v10/index.ts
export { archiveWorkflows, archiveHabits, /* ... */, archiveAll } from "./Archiver";
export type { ArchiveSection, ArchiveManifest } from "./types";
```
Worktree A imports from `@/migration/v10`. If A starts before B, A stubs the
export action with a TODO; once B merges, A removes the stub. **This is a
soft dependency** — A and B are independent worktrees.
## Conflicts to watch
**None.** Worktree B touches only new files in `src/migration/v10/` and
`src/__tests__/migration/v10/`. No existing code is modified.
The only file you write outside that namespace is potentially nothing — even
the orchestrator export goes through `src/migration/v10/index.ts`.
## Open questions that affect THIS worktree
- **Q2 — Sub-plugin commitment:** the default is YES, 4 sub-plugins. If the
user changes their mind and wants to delete a feature outright (no sub-plugin
migration), you can SKIP the corresponding archive function — there's no
point archiving data nobody will ever consume. Wait for confirmation if this
changes before you ship `archiveTimerData` etc.
The other 4 questions don't affect this worktree.
## Useful existing utilities
- `src/services/timer-export-service.ts` — already exports timer data to JSON. You can probably import its serialization logic for `archiveTimerData`.
- `src/components/features/quick-capture/modals/QuickCaptureModalWithSwitch.ts` (line ~1006) — has the existing settings shape for quick-capture, useful reference for `archiveOrphans`.
- `src/common/setting-definition.ts` (line ~995, `DEFAULT_SETTINGS`) — list of every settings field, useful for `archiveOrphans` exclusion list.
## Don't do these things
- **Don't write to the vault.** Pure functions only. Phase 3 / Worktree D.5
handles I/O via the migration registry.
- **Don't import obsidian's `Notice`, `Modal`, or `App`.** Your functions take
a plugin instance and return data. Nothing more.
- **Don't add dependencies.** Use plain JSON.stringify / template strings.
- **Don't OVERLY pretty-print the markdown.** A simple `# {section} {N}`
followed by `## {item.name}` for each item is fine. The markdown is a
fallback view; the JSON is the source of truth for sub-plugin importers.
- **Don't try to make the archive "diff-friendly".** Phase 3 is one-shot;
there's no need to support incremental archives.
## When you're done
```bash
git push origin refactor/v10-phase1-archiver
gh pr create --base master --title "feat(migration): v10 Archiver pure functions (Phase 1, Worktree B)"
```
After merge, Worktree A can remove its stub imports if it shipped with one.
Worktree D.5 (Phase 3 confirmation modal) will wire your `archiveAll` into
the MigrationRegistry as the `v10-archive` step.

View file

@ -0,0 +1,233 @@
# Worktree C — Phase 2 Read-Only Mode + Beta (9.15.0)
## What you're doing
Phase 1 (Worktrees A + B) shipped 9.14.0 with deprecation banners. Phase 2 is
the **hard warning** week: deprecated settings tabs become read-only (inputs
disabled, only the Export button works), deprecated commands fire a one-shot
Notice on first use per session, and v10 enters beta via BRAT.
This is the last stop before the cliff. After your worktree merges and ships
9.15.0, the beta channel gets 10.0.0-beta.1 and a ~5 day soak period.
## Phase 0 + Phase 1 ground
You depend on **Worktree A merged to master**. Verify before starting:
```bash
git fetch
git log master --oneline | grep "deprecation banners"
# Expect: "feat(settings): deprecation banners (Phase 1, Worktree A)" present
```
If not present, **do not start.** Worktree C cannot run in parallel with A —
you'll fight for the same 14 settings tab files.
Worktree B (Archiver) can be merged or pending; you only need its functions
for the export buttons (which are already wired by Worktree A).
## Setup
```bash
git fetch
git worktree add ../tg-phase2-readonly master
cd ../tg-phase2-readonly
git checkout -b refactor/v10-phase2-readonly
npm install
npm test -- --testPathPattern="integration/"
# Expect: 70+ passing
```
## Files you'll modify
The same 14 deprecated tab files Worktree A injected banners into. You're
adding a `readOnly` mode to each one:
1-14. All `src/components/features/settings/tabs/{Workflow,Habit,Reward,TaskTimer,TimelineSidebar,Ics,Index,DesktopIntegration,About}SettingsTab.ts` (and the 5 view-config sections)
Plus:
- `src/components/features/settings/components/DeprecationBanner.ts` (banner copy update)
- `src/common/deprecation-messages.ts` (add Notice strings for commands)
- `src/index.ts` (wrap deprecated commands with one-shot Notice)
- `src/managers/changelog-manager.ts` (10.0.0-beta.1 announcement modal)
- `manifest-beta.json` (bump to 10.0.0-beta.1)
## Tasks (in order)
### Task 1 — Add `readOnly` mode to DeprecationBanner (~½ day)
Extend the banner component (from Worktree A):
```ts
export interface DeprecationBannerProps {
// ... existing fields from Phase 1
readOnly?: boolean; // NEW
}
```
When `readOnly: true`, the banner copy upgrades from "moves to X in v10" to
"**read-only — export now**" and the "Export" button gets primary button
styling. Visual: same yellow background, but more urgent affordance.
Update CSS in `src/styles/setting.scss` for the new state.
### Task 2 — Disable inputs in 14 deprecated tabs (~2 days)
For each tab/section, walk the `Setting()` chains and call `.setDisabled(true)`
on each input when the tab is in read-only mode. The `Export` button stays
enabled. Add a `disabled` prop or a flag at the top of each render function.
Pattern:
```ts
// Before
new Setting(containerEl)
.setName("Enable workflows")
.addToggle(t => t.setValue(plugin.settings.workflow.enableWorkflow)
.onChange(async v => { ... }));
// After (Phase 2)
const readOnly = true; // hardcoded — Phase 3 deletes the tab entirely
new Setting(containerEl)
.setName("Enable workflows")
.addToggle(t => t.setValue(plugin.settings.workflow.enableWorkflow)
.setDisabled(readOnly)
.onChange(async v => { if (readOnly) return; ... }));
```
Pass the `renderDeprecationBanner` call `{readOnly: true, ...}`.
For sectioned tabs (Ics, Index, DesktopIntegration), only the deprecated
sections become read-only — the rest of the tab stays interactive.
### Task 3 — Wrap deprecated commands with first-use Notice (~1 day)
`src/index.ts` — for each command that targets a deprecated feature, wrap the
existing callback with a one-shot Notice:
```ts
// New helper at the top of registerCommands or in a small util
const _deprecationWarned: Record<string, boolean> = {};
function warnDeprecatedOnce(commandId: string, message: string): void {
if (_deprecationWarned[commandId]) return;
_deprecationWarned[commandId] = true;
new Notice(message, 6000);
}
// Wrap each deprecated command's callback
this.addCommand({
id: "create-quick-workflow",
name: t("Create Quick Workflow"),
editorCallback: async (editor, ctx) => {
warnDeprecatedOnce(
"create-quick-workflow",
t("This command is removed in v10. Install task-genius-workflow."),
);
// existing logic still runs
return createQuickWorkflowCommand(plugin, editor, ctx);
},
});
```
The 11 deprecated commands to wrap:
- 6 workflow commands from `src/commands/workflowCommands.ts`
- 5 task-timer commands (find via `grep -l "task-timer-" src/index.ts`)
- 1 reindex-habits command
Memory is per-session (cleared on plugin reload). Don't store in settings.
### Task 4 — Update Notice strings in deprecation-messages.ts (~½ day)
Add new keys for each command-level Notice. Centralize so locales translate
once.
### Task 5 — Sub-plugin v0.1.0 release coordination (out of repo, ~1 day)
This is the part that touches OTHER repos:
- `task-genius-workflow` v0.1.0 — release with a **one-time bootstrap importer**
that reads from main plugin's live `data.json` (not yet from `Task Genius
Archive/`, since 9.15 hasn't archived anything yet)
- `task-genius-habits` v0.1.0 — same pattern
- `task-genius-timer` v0.1.0 — same pattern
- **`task-genius-calendar-sync` is HELD** — riskiest one (OAuth tokens),
ships in Phase 4 (Worktree E)
Each sub-plugin's bootstrap importer logs which fields it imported and writes
a marker `<sub-plugin-data-dir>/.imported-from-main-plugin` so it doesn't
double-import on next load.
The sub-plugin repos don't exist yet — Worktree C creates them. Use the
main plugin's tooling (esbuild config, jest config, manifest format) as a
template.
### Task 6 — Cut 10.0.0-beta.1 (~½ day)
`manifest-beta.json` — bump to `10.0.0-beta.1`. Worktree D will own the
actual 10.0.0 work; this commit just opens the beta channel so testers can
opt in early.
`src/managers/changelog-manager.ts` — add a beta-only announcement: "v10
beta is here. Migration modal will appear on first launch — please test on
a backup vault first."
## Definition of Done
| # | Criterion | How to verify |
|---|---|---|
| 1 | All 14 deprecated tabs/sections show inputs as disabled | Manual: open Settings, try to toggle anything in Workflows tab — should be grayed out |
| 2 | Export buttons in each tab still functional | Manual: click each export button, verify archive folder updates |
| 3 | Banner copy updated to "read-only — export now" | Manual visual check |
| 4 | Each deprecated command fires a Notice on first use per session | Manual: invoke `Create Quick Workflow` twice — Notice once, second time silent |
| 5 | Notice memory clears on plugin reload | Manual: reload plugin, invoke command again — Notice fires again |
| 6 | 3 sub-plugins (workflow, habits, timer) released to community plugins or BRAT | Out of repo — verify by installing fresh and confirming bootstrap importer runs |
| 7 | 10.0.0-beta.1 published via BRAT | `manifest-beta.json` shows `10.0.0-beta.1`, BRAT installs it, real beta tester confirms it loads |
| 8 | TypeScript build clean | `npm run build` |
| 9 | Phase 0 integration tests still pass | `npm test -- --testPathPattern="integration/"` |
| 10 | No regression in non-deprecated tabs | Manual: open General/Tasks/Views, edit a setting, verify it persists |
## Conflicts to watch
- **Worktree D (Phase 3)** will DELETE the 14 tabs you're modifying. Same as
the A→D conflict — Phase 3 starts after Phase 2 merges, no overlap.
- **Worktree B (Archiver)** — your Export buttons call Worktree B's functions.
If B hasn't merged, the buttons remain stubbed (Worktree A's stubs from
Phase 1). Don't block on B; it'll be ready by the time you finish.
## Open questions that affect THIS worktree
- **Q4 — Cliff version (4 weeks vs 8 weeks):** the default is 4 weeks. If the
user wants 8, your worktree gets a longer beta soak (10.0.0-beta.1 stays in
the field for ~2 weeks instead of ~5 days). The work itself doesn't change.
- **Q5 — Beta tester recruitment:** Worktree A pinned the GitHub Discussion.
By the time you start, recruitment should have produced ~5-10 testers. If
it hasn't, push the discussion harder before cutting beta.
## Useful existing utilities
- The Obsidian `Setting` class' `.setDisabled(true)` method — works on every
input type (toggle, text, dropdown, slider, button)
- `Notice` from obsidian — your one-shot Notice helper wraps this
- `manifest-beta.json` — already exists, just bump the version field
## Don't do these things
- **Don't delete any tabs.** Worktree D owns deletions.
- **Don't modify the dataflow layer.** All your changes are UI + command callbacks.
- **Don't make the Notice persistent across sessions.** Per-session is the
contract — users who reload the plugin should see the warning again.
- **Don't ship a 10.0.0 manifest.** Only `manifest-beta.json` gets bumped.
`manifest.json` stays at `9.15.0`.
- **Don't translate Notice strings to non-English locales.** Worktree E's
locale prune handles parity.
## When you're done
```bash
git push origin refactor/v10-phase2-readonly
gh pr create --base master --title "feat(settings): read-only deprecated tabs + first-use command notice (Phase 2, Worktree C)"
```
After merge, **Worktree D is unblocked**. Tag `9.15.0` and cut
`10.0.0-beta.1` via BRAT. The 5-day beta soak begins.

View file

@ -0,0 +1,459 @@
# Worktree D — Phase 3 Cliff (10.0.0)
## What you're doing
This is **the cliff**. Everything Phase 1 + Phase 2 warned about, you actually
do. Settings tabs go from 25 to 7. Commands go from 43 to 16 with default
hotkeys. Quick-capture modals go from 5 to 1. Views go from 15 to 5 core + 3
widget-only. Onboarding goes from 36 files to 5. Roughly 50% of the plugin's
LOC gets deleted. The migration confirmation modal runs on first launch and
archives every deprecated feature's persistent data to `<vault>/Task Genius
Archive/`.
This is also the largest worktree by far. It's split into **5 sub-PRs** that
land sequentially on the worktree branch. You can run them as 5 separate
commits or 5 separate Sub-PRs against the worktree branch — whichever fits
your workflow.
## Phase 0/1/2 ground
You depend on **Worktree C merged to master**. Verify before starting:
```bash
git fetch
git log master --oneline | head -10
# Expect: "feat(settings): read-only deprecated tabs..." (Worktree C) is the most recent v10 commit
# Expect: Worktree A (banners) and Worktree B (Archiver) commits are also present
```
The Phase 0 deferred items from `PHASE0_DEFERRED.md` are picked up here:
- **Item 1 (delete `src/pages/TaskView.ts`)** → assigned to **D.4**
- **Item 2 (fold `src/utils/settings-migration.ts` into the registry)** → assigned to **D.2**
Read [`../PHASE0_DEFERRED.md`](../PHASE0_DEFERRED.md) before starting either
sub-PR for the resolution paths.
## Setup
```bash
git fetch
git worktree add ../tg-phase3-cliff master
cd ../tg-phase3-cliff
git checkout -b refactor/v10-phase3-cliff
npm install
npm test -- --testPathPattern="integration/"
# Expect: 70+ passing
```
## The 5 sub-PRs
Each sub-PR lands on `refactor/v10-phase3-cliff`. They MUST land in this
order due to `src/index.ts` conflicts:
```
D.2 (commands) → D.1 (settings tabs) → D.3 (quick capture) → D.4 (views + deletions) → D.5 (confirmation modal)
```
D.2 must be first because it owns the bulk of the `src/index.ts` rewrite.
D.5 must be last because it depends on every other sub-PR being stable.
---
### D.2 — Command palette + hotkeys (43 → 16)
**Why first:** owns the largest stretch of `src/index.ts`. Other sub-PRs
rebase onto it.
**Files:**
- `src/index.ts` (command registration block — major rewrite)
- `src/commands/*.ts` (delete: `completedTaskMover.ts`, `sortTaskCommands.ts`, `taskCycleCommands.ts`, `taskMover.ts`, `workflowCommands.ts`)
- `src/commands/v10/` (new directory) — 16 new consolidated command files
**Plus PHASE 0 DEFERRED Item 2** — fold `src/utils/settings-migration.ts`
into the registry. Resolution path:
1. Move `repairStatusCycles`, `validateStatusCycle`, `sortCyclesByPriority`, `findDuplicateCycleIds``src/utils/status-cycle-resolver.ts` (already exists)
2. Inline `migrateToMultiCycle` body into `src/utils/migration/steps/legacy-bundle-0.ts` (no longer imported)
3. Remove the W1 fallback in `src/index.ts:2003-2010` (the registry has atomic semantics — fallback is dead defense)
4. Delete `src/utils/settings-migration.ts`
5. Verify `npm test` and `npm run build`
**The 16 new commands** (renaming + consolidation map from main plan §3.4):
| New ID | Old IDs absorbed | Default hotkey |
|---|---|---|
| `tg:capture` | `quick-capture`, `minimal-quick-capture`, `toggle-quick-capture`, `toggle-quick-capture-globally`, `quick-file-create` | `Mod+Shift+T` |
| `tg:capture-here` | (new) | — |
| `tg:mark-done` | `cycle-task-status-forward` (when on task line) | `Mod+Enter` (task line) |
| `tg:mark-cycle` | `cycle-task-status-forward`, `cycle-task-status-backward` | — |
| `tg:set-priority` | all 12 priority commands collapsed into one picker | `Mod+Alt+P` |
| `tg:remove-priority` | `remove-priority` | — |
| `tg:open-view` | `open-task-genius-view` | `Mod+Shift+G` |
| `tg:open-inbox` | (new) | `Mod+Shift+I` |
| `tg:open-forecast` | (new) | `Mod+Shift+F` |
| `tg:open-review` | (new) | — |
| `tg:move-tasks` | all 6 task-mover commands → one picker for scope | — |
| `tg:sort-tasks` | `sort-tasks-by-due-date`, `sort-tasks-in-entire-document` | — |
| `tg:reindex` | `force-reindex-tasks` (and removes `reindex-habits`) | — |
| `tg:open-settings` | `open-task-genius-settings-modal` | — |
| `tg:open-archive` | (new — reveals `Task Genius Archive/` in file explorer) | — |
| `tg:setup` | `open-task-genius-setup` | — |
**Removed entirely:** `open-timeline-sidebar-view`, `open-task-genius-changelog`
(auto-opens on version change), 5× `task-timer-*`, 6× `workflow-*`. The 11
auto-move commands collapse into a setting toggle in the Tasks tab — **not in
the palette at all**.
**Backward compatibility:** keep old command IDs as aliases for **one version
only** (10.0.0). Drop in 10.0.1 via Worktree E. This preserves manually-bound
hotkeys for surviving commands during the upgrade.
**Discovery:** after the migration modal (D.5), show a one-screen "What
changed" sheet listing the 5 default hotkeys, the 4 picker collapses, and
the sub-plugin pointers. Use the existing `ChangelogManager` modal infrastructure.
**DoD subset:** `npm test`, `npm run build`, all 16 new commands appear in
the palette with the new names, manually-bound hotkeys for surviving commands
still work via aliases.
---
### D.1 — Settings tab rewrite (25 → 7)
**Files:**
- `src/components/features/settings/SettingsModal.ts` (`renderTabContent` switch — 25 cases → 7)
- `src/components/features/settings/tabs/*` (delete 14, rewrite 7)
- `src/components/features/settings/index.ts` (exports)
- `src/components/features/settings/components/DeprecationBanner.ts` (delete — banners are gone in v10)
- `src/common/deprecation-messages.ts` (delete)
- `src/utils/ObsidianUriHandler.ts:117` (switch to redirect map)
- `src/utils/uri-tab-redirects.ts` (new)
- `src/migration/v10/TabIdMap.ts` (new)
- All `src/components/features/settings/tabs/{Workflow,Habit,Reward,TaskTimer,TimelineSidebar}*.ts` (delete)
**The 7 new tabs:**
| # | New tab | Absorbs old tabs |
|---|---|---|
| 1 | **General** | `index`, `file-filter`, `interface` (parts) |
| 2 | **Capture** | `quick-capture`, `time-parsing`, `date-priority` (parts) |
| 3 | **Tasks** | `progress-bar`, `task-status`, `task-handler`, `date-priority` (parts) |
| 4 | **Views** | `view-settings`, `calendar-views`, `task-filter` |
| 5 | **Projects & Tags** | `project` |
| 6 | **Integrations** | `ics` (read-only only), `mcp-integration`, `bases-support`, `workspaces`, `desktop-integration` |
| 7 | **About** | `about`, `beta-test` + "Open archive folder", import/export, tombstones list |
**Deleted tabs (no merge):** `workflow`, `habit`, `reward`, `task-timer`,
`timeline-sidebar`. Their content was archived in D.5.
**URI redirect map:** `src/utils/uri-tab-redirects.ts` — see main plan §3.8
for the full table. Existing URI bookmarks pointing to deprecated tabs land
on `about#archived-X` with a Notice explaining where the data went.
**DoD subset:** Settings opens in ≤500ms. All 7 tabs render valid content on
an empty vault. Existing URI bookmarks still work (manual smoke test).
---
### D.3 — Quick capture modal consolidation (5 → 1)
**Files:**
- `src/components/features/quick-capture/modals/{QuickCaptureModal,MinimalQuickCaptureModal,MinimalQuickCaptureModalWithSwitch,BaseQuickCaptureModal}.ts` (delete)
- `src/components/features/quick-capture/modals/QuickCaptureModalWithSwitch.ts` → rename to `QuickCaptureModal.ts`
- New `Mode` strip UI inside the survivor (Full / Minimal / Daily)
- `src/utils/migration/steps/v10-quick-capture-modes.ts` (new — registers as a tombstone+transform)
- `src/index.ts` (command callback updates — `tg:capture` opens the unified modal)
**Three modes inside one modal:**
```
┌─────────────────────────────────────┐
│ [● Full] [Minimal] [Daily] [×] │ ← mode strip
├─────────────────────────────────────┤
│ ▌ Task content... │ ← always: text input, autofocus
│ │
│ ─── Below this line shown in Full ──│
│ Project ▾ Tags ▾ Priority ▾ │
│ Due ▾ Start ▾ Scheduled ▾ │
│ Target file: <path or daily-note>
│ Append / Prepend / Replace ▾ │
│ [Cancel] [Capture ↵] │
└─────────────────────────────────────┘
```
**Settings migration step `v10-quick-capture-modes`:**
```ts
// Old: separate command bound to MinimalQuickCaptureModalWithSwitch
// Old: lastUsedMode tracked per modal class
plugin.settings.quickCapture.mode = mapMode(plugin.settings.quickCapture.lastUsedMode);
delete plugin.settings.quickCapture.lastUsedMode;
delete plugin.settings.quickCapture.minimalModeSettings; // fields merged up one level
```
Known degradation: users who configured different file targets for full vs
minimal lose that distinction — call out in changelog.
**DoD subset:** capture flow works in all 3 modes, settings migration handles
existing `lastUsedMode` correctly, default hotkey (`Mod+Shift+T`) opens the
modal.
---
### D.4 — View consolidation + onboarding compression + deletions
**This is the biggest sub-PR by LOC.** It also picks up Phase 0 deferred
Item 1 (delete `src/pages/TaskView.ts`).
**Files (deletion sweep):**
- `src/components/features/{gantt,quadrant,habit}/*` (delete entire dirs — except keep a small `QuadrantWidgetView.ts` for codeblock embedding)
- `src/components/features/timeline-sidebar/*` (delete)
- `src/components/features/onboarding/**` (delete 31 of 36 files, add 4 simplified)
- `src/managers/{habit-manager,reward-manager,timer-manager,electron-quick-capture}.ts` (delete)
- `src/services/{timer-export-service,timer-format-service,timer-metadata-service}.ts` (delete)
- `src/managers/calendar-auth-manager.ts` (delete — moves to calendar-sync sub-plugin)
- `src/providers/*` (delete entire dir → calendar-sync sub-plugin)
- `src/parsers/holiday-detector.ts` (delete)
- `src/dataflow/sources/FileSource.ts` (delete)
- `src/editor-extensions/workflow/*` (delete entire dir)
- `src/editor-extensions/date-time/task-timer.ts` (delete)
- `src/pages/TaskView.ts` (delete after porting dirty changes — see PHASE0_DEFERRED.md Item 1)
**Files (additions):**
- `src/widgets/registerWidgets.ts` (add TableWidget, QuadrantWidget)
- `src/widgets/views/TableWidgetView.ts` (new, port from `src/components/features/table/`)
- `src/widgets/views/QuadrantWidgetView.ts` (new, ~200 LOC — codeblock embedding only)
- `src/migration/v10/ViewMigration.ts` (new)
- `src/utils/migration/steps/v10-view-cleanup.ts` (new — tombstone for removed views)
- `src/common/setting-definition.ts` (`viewConfiguration` defaults: 15 → 5)
- `src/components/features/onboarding/steps/{WelcomeStep,CaptureStep,ViewsStep,DoneStep}.ts` (new — 4 simplified onboarding steps)
**View consolidation (15 → 5 core + 3 widget-only):**
Core (live in FluentTaskView sidebar): `inbox`, `forecast`, `projects`, `tags`, `review`
Widget-only (no sidebar entry, codeblock + `tg:open-view` picker only): `calendar`, `kanban`, `table`
Removed: `gantt`, `quadrant`, `habit`, `working-on`, `flagged`
**Phase 0 deferred Item 1 (port TaskView.ts changes to FluentTaskView):**
The user has uncommitted modifications in `src/pages/TaskView.ts` adding
multi-cycle support to the "switch status" context menu. Before deleting
TaskView.ts:
1. Read the dirty diff (`git diff src/pages/TaskView.ts`). The relevant block
is the "switch status" submenu builder around line ~1263-1440.
2. Find the equivalent location in `src/pages/FluentTaskView.ts` and port the
multi-cycle logic. The new code reuses these helpers from
`src/utils/status-cycle-resolver.ts`:
- `findApplicableCycles(currentMark, statusCycles)`
- `getAllStatusNames`
- `getNextStatusPrimary`
- `getAllStatusMarks`
3. Move `TASK_VIEW_TYPE` constant from `src/pages/TaskView.ts` to a new file
`src/common/view-types.ts` (stale leaves in user vaults still need to be
detached on next load — see `src/index.ts:1825` and `:2116`).
4. Update import sites of `TASK_VIEW_TYPE`:
- `src/index.ts:72` (also remove the `TaskView` symbol from this import)
- `src/components/features/fluent/FluentIntegration.ts:13`
5. Delete the `instanceof TaskView` block at `src/index.ts:2119` — unreachable
since the class is no longer registered.
6. Delete `src/pages/TaskView.ts`.
7. Run typecheck + integration suite.
**Migration step `v10-view-cleanup`** (`src/migration/v10/ViewMigration.ts`):
```ts
const REMOVED = ["gantt", "quadrant", "habit", "working-on", "flagged"];
const DEMOTED = ["calendar", "kanban", "table"];
// 1. Snapshot removed view configs into Archive/views/removed-views.json (Worktree B's archiver)
// 2. For removed views with non-default filterRules, create a saved filter preset
// on the closest core view (working-on → inbox preset, flagged → forecast preset)
// 3. Strip removed entries from plugin.settings.viewConfiguration
// 4. For demoted views, set type:"widget" and remove from FluentTaskView sidebar list
// 5. Walk plugin.settings.workspaces.byId[*].settings.fluentActiveViewId; replace any removed-view ID with "inbox"
// 6. Walk plugin.settings.workspaces.byId[*].settings.hiddenModules and prune removed IDs
```
**Steps 5 + 6 are the WORKSPACE_ONLY_KEYS trap** — workspaces in
`plugin.settings.workspaces.byId[*]` must be cleaned in the same migration
step or workspaces will boot to a broken view.
**Onboarding compression (36 → 5 files):**
```
src/components/features/onboarding/
├── OnboardingView.ts ← view shell (kept, slimmed)
├── OnboardingController.ts ← state machine (kept, simplified to 4 steps)
├── steps/
│ ├── WelcomeStep.ts ← 1. Welcome + import-from-archive prompt
│ ├── CaptureStep.ts ← 2. Pick target file, hotkey hint, sample capture
│ ├── ViewsStep.ts ← 3. Pick which of 5 core views to show
│ └── DoneStep.ts ← 4. "You're ready" + docs link
└── ui/Layout.ts ← shared step layout (kept, simplified)
```
Delete every `Fluent*Step.ts`, `UserLevelStep`, `ModeSelectionStep`,
`ConfigPreviewStep`, `SettingsCheckStep`, `PlacementStep`, `IntroStep`,
`TaskGuideStep`, all of `steps/intro/`, `steps/guide/`, `steps/preview/`,
`previews/`, `TaskCreationGuide.ts`. **No more Beginner/Advanced/Power
branching** — one linear flow.
**DoD subset:** all 5 core views render with an empty vault, all workspaces
boot to a valid `fluentActiveViewId`, onboarding completes in ≤4 steps,
LOC reduction visible (`find src/ -name "*.ts" | xargs wc -l` should be
notably lower).
---
### D.5 — Migration confirmation modal
**Files:**
- `src/migration/v10/ConfirmationModal.ts` (new)
- `src/utils/migration/steps/v10-archive.ts` (new — wraps Worktree B's `archiveAll`)
- `src/index.ts` (first-launch detection in onload)
**The migration confirmation modal:**
On first launch of 10.0.0, BEFORE any view renders, show a blocking modal:
```
┌────────────────────────────────────────────┐
│ Task Genius is upgrading to v10. │
│ │
│ The following will be archived: │
│ • 47 workflows → Task Genius Archive/ │
│ workflows/ │
│ • 12 habits → habits/ │
│ • 3 ICS sources → calendar-sync/ │
│ • 5 custom views → views/ │
│ │
│ Archive folder: [Task Genius Archive ] │
│ │
│ [Cancel & rollback to v9] [Preview] │
│ [Apply ▶] │
└────────────────────────────────────────────┘
```
Driven by `MigrationRegistry.run(settings, {dryRun: true})` for the Preview
button, then `dryRun: false` on Apply. Cancel exits Obsidian without
persisting (leaves `data.json` untouched, instructions on how to revert).
**Wire `archiveAll` (from Worktree B) into the registry as `v10-archive`:**
```ts
// src/utils/migration/steps/v10-archive.ts
import { archiveAll } from "@/migration/v10/Archiver";
import type { MigrationStep } from "../MigrationRegistry";
export const v10ArchiveStep: MigrationStep = {
id: "v10.0.0-archive",
targetVersion: "10.0.0",
kind: "transform",
description: "Archive deprecated feature data to <vault>/Task Genius Archive/",
apply: async (settings, ctx) => {
// The migration is two-part:
// 1. Run archiveAll() to compute the archive contents
// 2. Have the modal write them to the vault (since the registry is pure)
// The "writing" is done by the ConfirmationModal in apply phase, NOT here.
// This step's job is just to mark that the archive happened.
return { changed: true, details: ["v10 archive scheduled"] };
},
};
```
**Beta vs stable behavior:** in `10.0.0-beta.X`, the modal defaults to
"preview only, do not apply" — user must explicitly click Apply. In stable
`10.0.0`, Apply is the primary button.
**DoD subset:** the §3.10 acceptance test from the main plan — the "single
hardest test":
> A user upgrades from 9.13.1 with: 47 workflows, 12 habits, 3 ICS sources,
> 2 OAuth calendars, 5 custom views, 3 workspaces, all 25 settings tabs touched
> at least once. After upgrading to 10.0.0, they:
> 1. See the migration modal within 3 seconds.
> 2. Click Apply.
> 3. See their Inbox.
> 4. Capture a task with `Mod+Shift+T`.
> 5. Mark it done.
> 6. Open `Task Genius Archive/` and see all their data.
>
> If any of those six steps requires reading docs, opening Settings, or
> restarting Obsidian — **it's not done.**
---
## Definition of Done (worktree-level)
| # | Criterion | How to verify |
|---|---|---|
| 1 | The §3.10 acceptance test from the main plan | Manual smoke on a real vault |
| 2 | First-time user enables plugin → first captured task in ≤60 seconds, ≤5 clicks | Manual on a fresh vault |
| 3 | Onboarding completes in ≤4 steps, ≤90 seconds | Manual |
| 4 | Settings opens in ≤500ms | Performance tab in dev tools |
| 5 | All workspaces boot to a valid `fluentActiveViewId` (no removed view IDs) | `git grep -l "removed view" src/__tests__` |
| 6 | All custom hotkeys for surviving commands still work | Manual |
| 7 | URI bookmarks pointing to deprecated tabs land somewhere reasonable with a Notice | Manual: navigate to `obsidian://task-genius?action=settings&tab=workflow` |
| 8 | No console errors during migration | `src/__tests__/migration/v10-migration.test.ts` |
| 9 | Main plugin LOC ≤ 42k (target 40k, ceiling 42k) | `find src/ -name "*.ts" -not -path "*__tests__*" -not -path "*__mocks__*" \| xargs wc -l \| tail -1` |
| 10 | 0 references to deleted modules | `git grep "from.*workflow"`, `git grep "from.*habit-manager"`, etc. — all should be empty in main plugin |
| 11 | All 5 core views render with an empty vault | Manual |
| 12 | All 16 commands appear in palette with new names | Manual |
| 13 | Phase 0 integration tests still pass | `npm test -- --testPathPattern="integration/"` |
| 14 | TypeScript build clean | `npm run build` |
## Conflicts within Worktree D
D.1-D.4 each touch `src/index.ts` differently:
- D.1 changes settings tab routing
- D.2 rewrites commands
- D.3 changes quick-capture commands
- D.4 deletes timer/workflow commands
**Sequence them: D.2 first (it owns the command block), then D.1, D.3, D.4
in any order, then D.5 last.**
## Open questions that affect THIS worktree
- **Q1 — Deprecation list:** if the user pulls anything back, the corresponding
D.4 deletion gets skipped. Confirm before D.4 starts.
- **Q3 — Default hotkeys:** the 5 defaults (`Mod+Shift+T`, etc.) are baked
into D.2. If the user wants different bindings or no defaults, change at
the top of D.2.
- **Q4 — Cliff version:** 4-week vs 8-week runway only affects WHEN you
start (after C merges, plus the beta soak period). Doesn't change scope.
## Useful existing utilities
- `src/managers/changelog-manager.ts` — for the "What changed" first-run sheet
- `src/utils/migration/MigrationRegistry.ts` (Phase 0) — already supports
atomic + dry-run, just add new steps
- `src/dataflow/cache/scope-map.ts` (Phase 0) — typed scope map for the
settings consolidation; D.1 should migrate the settings tab callers to
`onSettingsFieldsChanged()` as it rewrites them
- `src/widgets/core/BaseWidgetView.ts` — base for new TableWidgetView and
QuadrantWidgetView
- `src/widgets/codeblock/WidgetCodeBlockProcessor.ts` — codeblock embedding,
no changes needed
## Don't do these things
- **Don't ship without the migration confirmation modal.** D.5 is mandatory.
The whole point of Phase 0/1/2 was to set up the safety net for D.5 — it's
the user's only protection against data loss.
- **Don't keep `legacy` flags.** v10 is the cliff. No `enableV9Compatibility`
toggles.
- **Don't translate strings to non-English locales.** Worktree E's locale
prune handles parity.
- **Don't release `task-genius-calendar-sync` v0.1.0** — that's Worktree E.
Calendar sync is the riskiest sub-plugin (OAuth tokens) and gets held until
10.0.1 for an extra week of soak.
## When you're done
```bash
git push origin refactor/v10-phase3-cliff
gh pr create --base master --title "feat: v10 cliff - 16 commands, 7 settings tabs, 5 core views (Phase 3, Worktree D)"
```
After merge, **Worktree E is unblocked**. Tag `10.0.0`. The cleanup phase
begins.

View file

@ -0,0 +1,308 @@
# Worktree E — Phase 4 Cleanup (10.0.1)
## What you're doing
The cliff is behind us — 10.0.0 shipped with the migration confirmation modal,
the new 7-tab settings UI, the 16-command palette, the 5 core views, and
~50% of the LOC deleted. Worktree E is the **cleanup pass** that:
- Removes the migration code itself (its job is done)
- Drops the one-version command-ID aliases that Worktree D shipped for
backward compatibility
- Prunes orphan locale keys
- Releases `task-genius-calendar-sync` v0.1.0 (the riskiest sub-plugin, held
back from Phase 2 for an extra week of soak)
This is the smallest worktree by LOC. It's mostly bookkeeping with one
sub-plugin release coordination.
## Phase 0/1/2/3 ground
You depend on **Worktree D merged to master**. Verify before starting:
```bash
git fetch
git log master --oneline | head -5
# Expect: "feat: v10 cliff..." (Worktree D) is the most recent commit
git tag | grep "10.0.0$"
# Expect: 10.0.0 tag exists
```
Beta soak should have run for ~5 days minimum after `10.0.0-beta.1` was cut
in Phase 2. If real users on the beta channel are reporting bugs, FIX THOSE
FIRST before starting Worktree E — your work is post-stability cleanup, not
emergency response.
## Setup
```bash
git fetch
git worktree add ../tg-phase4-cleanup master
cd ../tg-phase4-cleanup
git checkout -b refactor/v10-phase4-cleanup
npm install
npm test -- --testPathPattern="integration/"
# Expect: 70+ passing
```
## Files you'll modify
- `src/migration/v10/Archiver.ts` (delete — its job is done, archive folder lives in user vaults forever)
- `src/migration/v10/index.ts` (update exports)
- `src/utils/migration/steps/v10-archive.ts` → tombstone-only marker
- `src/utils/migration/steps/v10-view-cleanup.ts` → tombstone marker
- `src/index.ts` (drop one-version command aliases that D.2 shipped)
- `src/__tests__/migration/v10/` (delete the Archiver tests since the source is gone)
## Files you'll create
- `scripts/prune-locale-orphans.mjs` (new — one-shot script)
- `src/__tests__/locale-parity.test.ts` (new — CI guard against regressions)
## Tasks (in order)
### Task 1 — Tombstone the migration code (~½ day)
The `v10-archive` and `v10-view-cleanup` migration steps did their job in
10.0.0. They should now become **no-op tombstones** that just record "already
applied" via `_meta.lastMigratedVersion`. The actual archive logic in
`src/migration/v10/Archiver.ts` is no longer needed.
For each of the two steps:
```ts
// Before (10.0.0)
export const v10ArchiveStep: MigrationStep = {
id: "v10.0.0-archive",
targetVersion: "10.0.0",
kind: "transform",
description: "...",
apply: async (settings, ctx) => { /* real work */ },
};
// After (10.0.1)
export const v10ArchiveStep: MigrationStep = {
id: "v10.0.0-archive",
targetVersion: "10.0.0",
kind: "tombstone", // changed from "transform"
description: "Archive migration applied in 10.0.0 (now no-op)",
apply: () => ({ changed: false, details: ["already applied"] }),
};
```
The reason to keep the step at all (instead of deleting it) is that users
upgrading from 9.x DIRECTLY to 10.0.1 (skipping 10.0.0) need the registry to
recognize the version slot. With a tombstone marker, the registry sees
`fromVersion < 10.0.0 < toVersion = 10.0.1` and runs the step — which now
no-ops. Without the marker, fresh-install users would also be fine, but
upgrading users wouldn't have a migration record at the right version.
Then delete `src/migration/v10/Archiver.ts` and its tests:
```bash
rm src/migration/v10/Archiver.ts
rm -rf src/__tests__/migration/v10/
```
Update `src/migration/v10/index.ts` to remove the Archiver exports.
### Task 2 — Drop one-version command aliases (~½ day)
Worktree D.2 kept old command IDs as aliases for one version (10.0.0) to
preserve manually-bound hotkeys. Now drop them:
```bash
git grep -n "addCommand.*id:.*['\"]old-command-id['\"]" src/index.ts
# Find the alias registrations and delete them
```
The aliases were:
- `quick-capture` → aliased to `tg:capture`
- `cycle-task-status-forward` → aliased to `tg:mark-cycle`
- ... etc., one alias per surviving command
These are documented in the D.2 sub-PR commit history. Read that commit to
see the full alias list.
Add a CHANGELOG entry: "Removed v9 command aliases. If you bound hotkeys to
old command IDs, rebind them to the v10 equivalents (see CHANGELOG 10.0.0)."
### Task 3 — Locale prune script (~½ day)
`scripts/prune-locale-orphans.mjs`:
```js
#!/usr/bin/env node
/**
* Prune locale orphans: delete keys in non-en locale files that don't
* exist in en.ts. Run manually before tagging 10.0.1.
*/
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const localeDir = path.join(__dirname, "..", "src", "translations", "locale");
const en = await import(path.join(localeDir, "en.ts"));
const enKeys = new Set(Object.keys(en.default));
const files = await fs.readdir(localeDir);
for (const file of files) {
if (file === "en.ts" || !file.endsWith(".ts")) continue;
// ... parse, prune, write back
}
console.log(`Pruned orphan keys from ${files.length - 1} non-en locales`);
```
Run it:
```bash
node scripts/prune-locale-orphans.mjs
```
This is a ONE-SHOT script. Don't add it to npm scripts or CI. The author
runs it manually when locale orphans accumulate.
### Task 4 — Locale parity Jest test (~½ day)
`src/__tests__/locale-parity.test.ts` — a tiny ratchet test that asserts
every key in `en.ts` exists in every other locale file. New strings added
to `en.ts` are allowed to be missing in other locales (they fall back to
en) — this only fails if a locale file has EXTRA keys that don't exist in
en.
Wait, that's wrong direction. Re-think:
- `en.ts` is the canonical superset
- Other locales should be SUBSETS of `en.ts` (missing translations fall back to en)
- If a non-en locale has a key that en doesn't, that's an orphan — assert it doesn't happen
```ts
import en from "@/translations/locale/en";
import zhCn from "@/translations/locale/zh-cn";
import zhTw from "@/translations/locale/zh-tw";
import ja from "@/translations/locale/ja";
import ru from "@/translations/locale/ru";
import uk from "@/translations/locale/uk";
import ptBr from "@/translations/locale/pt-br";
import enGb from "@/translations/locale/en-gb";
const enKeys = new Set(Object.keys(en));
describe("locale parity (W4 cleanup)", () => {
test.each([
["zh-cn", zhCn],
["zh-tw", zhTw],
["ja", ja],
["ru", ru],
["uk", uk],
["pt-br", ptBr],
["en-gb", enGb],
])("%s has no orphan keys not in en.ts", (name, locale) => {
const orphans = Object.keys(locale).filter((k) => !enKeys.has(k));
expect(orphans).toEqual([]);
});
});
```
This is the **automated** complement to the manual prune script. The script
runs occasionally; the test runs every PR.
### Task 5 — Release `task-genius-calendar-sync` v0.1.0 (~2 days, mostly out of repo)
This is the riskiest sub-plugin — OAuth tokens, token refresh, CalDAV write
operations. It was held back from Phase 2 for an extra week of soak after
v10.0.0 ships.
Tasks:
1. Create the new repo `task-genius-calendar-sync` (copy main plugin's tooling)
2. Migrate `src/providers/*`, `src/managers/calendar-auth-manager.ts` from the
main plugin (which deleted them in Worktree D.4)
3. Add a one-time importer that reads from `<vault>/Task Genius Archive/calendar-sync/caldav-sources.json`
4. Test with a real Google Calendar account (out of repo)
5. Release v0.1.0 to community plugins (or BRAT)
6. Pin a "calendar sync available" announcement to the GitHub Discussion
This task is largely external to the main plugin repo. The main-plugin side
is just verifying nothing reads from the deleted provider files (`git grep`).
### Task 6 — Bump version + tag (~10 minutes)
```bash
# In the main plugin repo
# Bump manifest.json from 10.0.0 to 10.0.1
# Bump versions.json (the Obsidian plugin version map)
# Commit
git tag v10.0.1
git push origin v10.0.1
```
## Definition of Done
| # | Criterion | How to verify |
|---|---|---|
| 1 | `src/migration/v10/Archiver.ts` deleted | File doesn't exist; `git grep "Archiver"` returns no production matches |
| 2 | Both v10 migration steps are tombstones (no-op) | Code review |
| 3 | One-version command aliases removed from `src/index.ts` | `git grep "alias.*10.0.0"` returns nothing |
| 4 | Locale prune script exists and runs without error | `node scripts/prune-locale-orphans.mjs` exits 0 |
| 5 | Locale parity Jest test exists and passes | `npm test -- --testPathPattern="locale-parity"` |
| 6 | `task-genius-calendar-sync` v0.1.0 published | Out of repo; check community plugins or BRAT |
| 7 | `manifest.json` is `10.0.1` | File contents |
| 8 | TypeScript build clean | `npm run build` |
| 9 | All Phase 0 + Phase 3 integration tests still pass | `npm test -- --testPathPattern="integration/"` |
| 10 | No deleted-module references | `git grep "from.*workflow"`, `git grep "from.*habit-manager"`, `git grep "from.*timer-manager"`, `git grep "from.*calendar-auth"` — all empty |
| 11 | Main plugin LOC ≤ 42k (still hitting the v10 ceiling) | `find src/ -name "*.ts" -not -path "*__tests__*" -not -path "*__mocks__*" \| xargs wc -l \| tail -1` |
## Conflicts to watch
**None.** By the time Worktree E starts, Phase 3 is merged and stable. No
other worktrees are in flight.
## Open questions that affect THIS worktree
- **Q2 — Sub-plugin commitment:** if the user changed their mind about
`task-genius-calendar-sync` (e.g. wants to delete OAuth/CalDAV outright
instead of extracting), Task 5 gets skipped. Confirm before starting.
The other 4 questions are settled by the time you reach Phase 4.
## Useful existing utilities
- `src/utils/migration/MigrationRegistry.ts` — already supports `kind: "tombstone"` (Phase 0)
- `src/translations/locale/*.ts` — existing locale files; the prune script
walks them
- `src/translations/helper.ts``t()` translation helper
## Don't do these things
- **Don't delete the migration steps entirely.** They become tombstones, not
deletions. Tombstones preserve the version slot for skip-version upgrades.
- **Don't translate orphan strings before pruning.** The prune script
determines which strings are orphans by comparing to en.ts. Translate AFTER
the prune, not before.
- **Don't add the prune script to CI or npm scripts.** It's a one-shot tool;
the author runs it manually.
- **Don't ship `task-genius-calendar-sync` without testing OAuth refresh on
a real account.** This is the highest-risk sub-plugin; an untested
refresh-token bug = users locked out of their calendars.
- **Don't bump the major version.** 10.0.1 is a patch release; 10.1.0 is
reserved for the first feature release post-cliff.
## When you're done
```bash
git push origin refactor/v10-phase4-cleanup
gh pr create --base master --title "chore: v10 cleanup - tombstones, locale prune, calendar-sync release (Phase 4, Worktree E)"
```
After merge, tag `v10.0.1` and the v10 refactor effort is COMPLETE. The
plugin is at ~40k LOC, the user has 5 core views + 16 commands + 7 settings
tabs + 1 quick-capture modal + a 4-step onboarding, and 4 sub-plugins are
available for users who want the deprecated features back.
Update CHANGELOG.md with a final summary entry and consider writing a blog
post / GitHub release note explaining the 6-week journey for users who
weren't in the loop.

View file

@ -1,5 +1,18 @@
# Task Genius v10 — Phase 1-4 Worktree Split
> **Update (2026-04-07):** the 5 worktrees described below have each been
> split into a self-contained brief in [`./.v10-worktrees/`](./.v10-worktrees/).
> Hand each brief to one agent / developer. This file remains as the umbrella
> overview; individual briefs have the actionable instructions.
>
> | Worktree | Brief |
> |---|---|
> | A — banners | [`.v10-worktrees/worktree-a-banners.md`](./.v10-worktrees/worktree-a-banners.md) |
> | B — Archiver | [`.v10-worktrees/worktree-b-archiver.md`](./.v10-worktrees/worktree-b-archiver.md) |
> | C — read-only | [`.v10-worktrees/worktree-c-readonly.md`](./.v10-worktrees/worktree-c-readonly.md) |
> | D — cliff cluster | [`.v10-worktrees/worktree-d-cliff.md`](./.v10-worktrees/worktree-d-cliff.md) |
> | E — cleanup | [`.v10-worktrees/worktree-e-cleanup.md`](./.v10-worktrees/worktree-e-cleanup.md) |
Operational follow-up to the v10 refactor plan
(`~/.claude/plans/dynamic-mixing-pie.md`). That document is the source of
truth for **what** to do in each phase. This document is the source of truth