chore: update harness state files (progress, handoff, feature list)

Records this session's completed work (#33, #36, #31, #39 consolidated
onto PR #51 alongside the prior session's #40/#41/#42/#48) and the
paused state of feat-009 (#38 i18n), which is blocked on a scope
decision from the user before any code is written.
This commit is contained in:
ClaudiaFang 2026-07-13 14:08:36 +00:00
parent 4eebebc765
commit 671ce93371
4 changed files with 105 additions and 52 deletions

View file

@ -7,3 +7,9 @@ over — don't let a single archive file grow without bound either.
- feat-001: Project Setup verified clean (npm install/lint/build/test) (commit 28f4f8e)
- feat-002: Settings UX bundle implemented — conflict modal resize + tabs (#42), connection status badge (#41), local ignore patterns (#40); 254 tests pass, lint/build clean (commit 28f4f8e, branch claude/settings-ux-improvements-260713)
- feat-005: Folder picker for root path / vault folder settings (#48) (commit c107979)
- feat-003: Symlinked directories no longer break pull discovery (#33) — root cause: local scan recursed into a directory symlink as if it were a real tree, producing bogus remote .gitignore 404s and a permanently-stuck "remote only" status (commit 4c8896b)
- feat-006: Tree-SHA-based refresh (#36) — status check now compares a locally-computed git blob SHA against each tree entry's SHA instead of a per-file getFile network request; diff content fetched on demand via new getBlob(sha,path) (commit 2ed5a43)
- feat-007: Clear error when requestUrl() itself rejects with an HTML body (#31) — some Obsidian versions eagerly JSON-parse inside requestUrl(), so a login/proxy HTML page rejected the whole call with a raw SyntaxError bypassing the existing parseJson() HTML-detection wrapper (commit a867217)
- feat-008: "What's new" modal after a version bump (#39) — hand-curated src/changelog.ts (separate from auto-generated CHANGELOG.md) with a `notable` flag per entry, numeric compareVersions(), WhatsNewModal shown once per upgrade (commit 4eebebc)
- All six of the above (feat-002/003/005/006/007/008) consolidated onto branch claude/fix-directory-symlink-pull-260713 → PR #51 (open, not yet merged) to keep the PR count down per user request; PRs #49/#50/#52/#53 closed/auto-resolved as superseded

View file

@ -1,5 +1,6 @@
{
"_note": "GitHub Issues (firstsun-dev/git-files-sync, Project #6) is the source of truth for the full backlog and priority/estimate fields. This file mirrors only the active feature and the next few candidates so an agent session has a local, offline checkpoint — sync it against `gh issue list --repo firstsun-dev/git-files-sync --state open` at the start of a session rather than treating it as authoritative.",
"_prPolicy": "User explicitly requested fewer PRs (2026-07-13): all issue work is consolidated onto branch claude/fix-directory-symlink-pull-260713 -> PR #51, not one PR per issue. Commit new work directly onto that branch unless told otherwise.",
"features": [
{
"id": "feat-001",
@ -14,16 +15,16 @@
"name": "Settings UX bundle (issues #40, #41, #42)",
"description": "Local ignore-pattern sync setting (#40), persistent connection status badge in settings (#41), and resized/tabbed conflict resolution modal (#42)",
"dependencies": ["feat-001"],
"status": "in-review",
"evidence": "Commit 28f4f8e on branch claude/settings-ux-improvements-260713 - lint/build/test pass (254 tests); pushed to origin, PR not yet opened"
"status": "done",
"evidence": "Consolidated onto PR #51 (branch claude/fix-directory-symlink-pull-260713); 302 tests pass"
},
{
"id": "feat-003",
"name": "fix: symbolic link pull fails (issue #33)",
"description": "Bug report with screenshot; needs repro/investigation before a fix can be scoped",
"description": "Directory symlinks were walked as real trees by local scanning code, causing bogus remote .gitignore 404s and a permanently-stuck 'remote only' status; also fixed a missing Windows symlinkSync type hint",
"dependencies": [],
"status": "not-started",
"evidence": ""
"status": "done",
"evidence": "Commit 4c8896b, consolidated onto PR #51"
},
{
"id": "feat-004",
@ -38,6 +39,46 @@
"name": "feat(settings): folder picker for root path / vault folder (issue #48)",
"description": "Searchable folder suggester for the Root path and Vault folder settings fields, replacing free-text entry",
"dependencies": [],
"status": "done",
"evidence": "Commit c107979, consolidated onto PR #51"
},
{
"id": "feat-006",
"name": "perf(refresh): use tree blob SHAs to avoid per-file content fetches (issue #36)",
"description": "Status refresh compares a locally-computed git blob SHA against each remote tree entry's SHA instead of one getFile per file; diff content fetched on demand via new getBlob(sha,path); symlink-aware hashing per real/follow/skip modes",
"dependencies": [],
"status": "done",
"evidence": "Commit 2ed5a43, consolidated onto PR #51; 302 tests pass"
},
{
"id": "feat-007",
"name": "fix: clear error when requestUrl() rejects with HTML (issue #31)",
"description": "Some Obsidian versions eagerly JSON-parse inside requestUrl() itself, so a login/proxy HTML page rejected the whole call with a raw SyntaxError bypassing the existing parseJson() HTML-detection wrapper; added the same detection to safeRequest's outer catch",
"dependencies": [],
"status": "done",
"evidence": "Commit a867217, consolidated onto PR #51"
},
{
"id": "feat-008",
"name": "feat: show new feature tips after update (issue #39)",
"description": "Hand-curated src/changelog.ts (separate from auto-generated CHANGELOG.md) with a notable flag per entry, numeric compareVersions(), WhatsNewModal shown once per version bump via a new lastSeenVersion setting",
"dependencies": [],
"status": "done",
"evidence": "Commit 4eebebc, consolidated onto PR #51"
},
{
"id": "feat-009",
"name": "feat: add i18n (multi-language) support (issue #38)",
"description": "Extract hardcoded UI strings (~47 in settings.ts, ~24 in SyncStatusView.ts) and 37 Notice() call sites (4 files, many with interpolated values) into an i18n key system with locale detection and English fallback",
"dependencies": [],
"status": "blocked",
"evidence": "Scope question asked, dismissed without an answer. No code written. Blocked on user picking a scope: (a) settings.ts only, Notices later; (b) settings.ts + all Notices now; (c) infra/mechanism only, defer string migration."
},
{
"id": "feat-010",
"name": "feat: add bitbucket support (issue #37)",
"description": "Add Bitbucket as a fourth GitServiceInterface provider; Bitbucket's API has no native blob SHA concept and is PR-based rather than direct-commit, so GitFile.sha semantics may need adjusting",
"dependencies": ["feat-009"],
"status": "not-started",
"evidence": ""
}

View file

@ -12,55 +12,54 @@ Completed work is archived in [archive/](./archive/), one file per calendar mont
## Current State
**Last Updated:** 2026-07-13 11:40
**Last Updated:** 2026-07-13 14:05
**Session ID:** session_01YYCTyZw7gUmJ7oh1VTmAqh
**Active Feature:** feat-002 - Settings UX bundle (issues #40, #41, #42)
**Active Feature:** feat-009 - i18n / multi-language support (issue #38) — paused, awaiting scope decision
## Status
### What's Done
- [x] feat-001 - Project Setup verified (see archive/2026-07.md)
- [x] feat-002 implementation - all three sub-features coded, tested, linted, built (see archive/2026-07.md)
- [x] feat-001..008 (project setup, settings UX bundle, folder picker, symlink pull fix, tree-SHA refresh, HTML-response error clarity, what's-new modal) — see archive/2026-07.md
- All consolidated onto branch `claude/fix-directory-symlink-pull-260713`**PR #51** (open, all CI green as of last check), per user's explicit request to keep the PR count down rather than one PR per issue.
### What's In Progress
- [ ] feat-002 - Open a PR for branch `claude/settings-ux-improvements-260713` and get it merged
- Details: commit 28f4f8e is pushed to origin; no PR opened yet
- Blockers: none, just needs `gh pr create`
- [ ] feat-009 - i18n / multi-language support (issue #38)
- Scope is large: ~47 hardcoded strings in `src/settings.ts`, ~24 in `src/ui/SyncStatusView.ts`, plus 37 `new Notice(...)` call sites across `src/logic/sync-manager.ts`, `src/settings.ts`, `src/main.ts`, `src/ui/SyncStatusView.ts` (many with interpolated values).
- Asked the user how deep to scope this (full settings.ts extraction only vs. settings+all Notices vs. infra-only-first); the question prompt was dismissed without an answer before the user ran `/firstsun-harness`. **Not yet started** — no i18n files created.
- Next step: get a scope decision from the user before writing any code, since a half-migrated i18n system (some strings extracted, most not) is worse than not starting.
### What's Next
1. Open PR for `claude/settings-ux-improvements-260713`, close issues #40/#41/#42 on merge
2. Pick up feat-003 (issue #33, symlink pull fails) — needs repro investigation first
3. Re-sync this file's backlog entries against `gh issue list --repo firstsun-dev/git-files-sync --state open` since new issues may have been filed (e.g. #48 folder picker was added mid-session)
1. Resolve feat-009's scope question with the user, then implement.
2. Issue #37 (Bitbucket provider support) — large, deferred until #38 lands (per agreed order: 39→38→37).
3. Re-sync against `gh issue list --repo firstsun-dev/git-files-sync --state open` — as of this session, #40/#41/#42/#48/#33/#36/#31/#39 are technically still "open" on GitHub but are all done in code and waiting on PR #51 to merge (they'll auto-close then). Remaining genuinely-unstarted issues: #47 (regex ignore lists), #45 (SonarQube findings), #38 (i18n, in progress), #37 (Bitbucket), #28 (non-engineering: community visibility).
## Blockers / Risks
- None currently.
- feat-009 (i18n) is blocked on a scope decision from the user — do not start writing `src/i18n/*` or touching `settings.ts` strings until that's confirmed, to avoid an inconsistent half-migration.
- PR #51 is large (6 issues' worth of changes). If the user wants to review/merge it before more work piles on, flag this rather than continuing to add commits indefinitely.
## Decisions Made
- **Discarded a stale local WIP for Obsidian 1.12.x compatibility**: origin/main already shipped this via PR #46 (`applyDestructiveStyle`, `typecheck-compat.mjs`) with a different mechanism. Local main was fast-forwarded to origin/main and the new features (#40/#41/#42) were manually re-applied on top of the correct base rather than merged via `git stash pop` (which would have conflicted/duplicated the compat layer).
- **feat-002 bundles three issues in one commit**: #40/#41/#42 touch overlapping files (`src/settings.ts`, `styles.css`, `tests/setup.ts`) closely enough that splitting into three atomic commits wasn't worth the risk of an intermediate broken state.
- **All work goes into one PR (#51)**: user explicitly said "不要那麼多pr merge" (don't want so many PRs). Branches `claude/tree-sha-status-refresh-260713` and `claude/fix-html-response-json-error-260713` were merged into `claude/fix-directory-symlink-pull-260713` and their PRs (#52, #53) closed/auto-resolved; `claude/settings-ux-improvements-260713` and `claude/folder-picker-settings-260713` were already merged in earlier the same way (PRs #49, #50 closed). **Any further issue work this session should commit directly onto `claude/fix-directory-symlink-pull-260713`, not a new branch.**
- **`src/changelog.ts` is hand-curated, separate from the auto-generated `CHANGELOG.md`**: semantic-release already maintains `CHANGELOG.md` from Conventional Commit messages, but it's commit-log-level detail (too granular for an end-user popup) and isn't shipped in the release assets (only `main.js`/`manifest.json`/`styles.css` are). The new modal needs a small, hand-written, user-facing "highlights" list instead — added as part of cutting a release, not auto-generated.
- **Fixed two duplication regressions mid-session** (SonarCloud gate is 3% on new code, learned the hard way on PR #49 earlier): deduped `TextComponent`/`TextAreaComponent` test mocks, and deduped the GitHub/Gitea `getBlob()` bodies into a shared `fetchGitHubStyleBlob()` base helper.
## Files Modified This Session
- `src/settings.ts` - connection status badge (#41), ignore patterns setting (#40)
- `src/ui/SyncConflictModal.ts` - Diff/Local/Remote tab switcher (#42)
- `src/logic/gitignore-manager.ts`, `src/main.ts` - local ignore pattern matching (#40)
- `styles.css` - badge + modal + tab styles
- `tests/setup.ts`, `tests/ui/setup-dom.ts` - expanded Obsidian mocks (TextAreaComponent, removeClass, configDir, etc.)
- `tests/logic/gitignore-manager.test.ts`, `tests/logic/sync-manager*.test.ts`, `tests/ui/SyncConflictModal.test.ts`, `tests/ui/SettingsConnectionStatus.test.ts` - new/updated tests
See archive/2026-07.md entries feat-003/005/006/007/008 for the full per-feature file lists. No files touched yet for feat-009 (i18n) — paused before any code was written.
## Evidence of Completion
- [x] Tests pass: `npx vitest run` → 254/254 passed
- [x] Type check clean: `npm run build` (tsc + Obsidian 1.11.0 compat typecheck + esbuild) → clean
- [x] Tests pass: `npx vitest run` → 302/302 passed (as of commit 4eebebc)
- [x] Type check clean: `npm run build` → clean
- [x] Lint clean: `npx eslint .` → 0 errors
- [ ] Manual verification in Obsidian: not done (no Obsidian instance available in this environment)
- [ ] Manual verification in Obsidian: not done (no Obsidian instance available in this environment) for any feature this session
## Notes for Next Session
- Branch `claude/settings-ux-improvements-260713` is pushed but has no PR yet — check with the user before opening one (they were asked and hadn't confirmed as of session end).
- `feature_list.json`'s backlog (feat-003..005) is a snapshot from 2026-07-13; re-check `gh issue list` before trusting it.
- Start by getting the user's answer on feat-009's scope (see "What's In Progress" above) before writing any i18n code.
- `feature_list.json`'s backlog is a snapshot from this session — re-check `gh issue list` before trusting it, though as of now it should be accurate (checked at end of session).
- Working branch for all further commits: `claude/fix-directory-symlink-pull-260713` (PR #51). Do not open a new branch/PR for the next issue unless the user says otherwise.

View file

@ -9,17 +9,18 @@
## Current Objective
- Goal: Implement issues #40 (local ignore patterns), #41 (settings connection status badge), #42 (resize conflict modal)
- Current status: Implemented, tested, linted, built clean; committed and pushed. PR not yet opened (pending user confirmation).
- Branch / commit: `claude/settings-ux-improvements-260713` @ 28f4f8e
- Goal: Work through open issues one at a time, all consolidated into a single PR (user explicitly asked for fewer PRs, not one per issue).
- Current status: 6 issues done and pushed (#33, #36, #31, #39, plus #40/#41/#42/#48 from an earlier session), all on one branch/PR. Issue #38 (i18n) was scoped but paused before any code was written — waiting on the user's answer to a scope question.
- Branch / commit: `claude/fix-directory-symlink-pull-260713` @ 4eebebc → **PR #51** (open, all CI checks green as of last check)
## Completed This Session
- [x] #42 - Resized conflict modal (`min(1100px,92vw) x min(85vh,800px)`, flex layout, removed 280px content cap, added Diff/Local/Remote tab switcher on narrow screens)
- [x] #41 - Persistent connection status badge in settings tab (Checking/Connected/Not connected), 800ms debounce on token/branch/URL/owner/repo edits, updates in place (no focus-stealing re-render)
- [x] #40 - "Ignore patterns" setting (multi-line, .gitignore-style), applied in `GitignoreManager.isIgnored()` additively alongside remote/local `.gitignore`
- [x] Filed issue #48 (folder picker for root path / vault folder settings) at user's request mid-session
- [x] Rebased local `main` onto `origin/main` to adopt the already-shipped Obsidian 1.12.x compat work (PR #46), discarding a stale duplicate local WIP, then re-applied the above three features cleanly on top
- [x] #31 - `fix: surface a clear error when requestUrl() itself rejects with HTML content` — some Obsidian versions eagerly JSON-parse inside `requestUrl()`, so a login/proxy HTML page rejected the whole call with a raw `SyntaxError`, bypassing the existing `parseJson()` HTML-detection wrapper. Added the same detection to `safeRequest`'s outer catch.
- [x] #33 - `fix: symlinked directories no longer break pull discovery` — a directory symlink is a single git blob, not a real tree; local scanning recursed into it as if it were, causing bogus remote `.gitignore` 404s and a permanently-stuck "remote only" status. Also fixed `createLocalSymlink`'s missing Windows `symlinkSync` type hint.
- [x] #36 - `perf(refresh): use tree blob SHAs to avoid per-file content fetches` — refresh now compares a locally-computed git blob SHA against each tree entry's SHA instead of one `getFile` network call per file; diff content is fetched on demand via a new `getBlob(sha, path)` per service. Symlink-aware hashing (real/follow/skip modes) per the issue's follow-up design.
- [x] #39 - `feat: show new feature tips after update` — new `src/changelog.ts` (hand-curated, separate from the auto-generated `CHANGELOG.md`) with a `notable` flag per entry, a `WhatsNewModal`, and a `lastSeenVersion` setting so the tip shows once per upgrade only.
- [x] Consolidated everything (this session's 4 issues + the prior session's #40/#41/#42/#48) onto one branch/PR (#51) per the user's explicit request to reduce PR count — closed/merged the now-redundant PRs #49/#50/#52/#53.
- [ ] #38 (i18n) - **paused, not started.** Counted scope (~47 strings in `settings.ts`, ~24 in `SyncStatusView.ts`, 37 `Notice()` call sites across 4 files) and asked the user how deep to scope it; the question prompt was dismissed without an answer before they ran `/firstsun-harness`. No `src/i18n/*` files exist yet — don't assume a design, ask again first.
## Verification Evidence
@ -27,33 +28,39 @@
|---|---|---|---|
| Lint | `npx eslint .` | 0 errors | Repo-wide, no exceptions |
| Type check + compat | `npm run build` | Pass | Includes `typecheck-compat.mjs` against Obsidian 1.11.0 |
| Tests | `npx vitest run` | 254/254 passed | 17 test files |
| Tests | `npx vitest run` | 302/302 passed | 22 test files |
| Duplication | `npx jscpd --min-lines 5 --min-tokens 50 src` | 14 clones, all pre-existing | Checked after every commit — SonarCloud's gate is 3% on *new* code, learned this the hard way earlier (PR #49 failed at 8.3%) |
| Manual (in Obsidian) | — | Not done | No Obsidian instance available in this environment |
## Files Changed
## Files Changed (this session, cumulative across the 4 issues)
- `src/settings.ts`, `src/ui/SyncConflictModal.ts`, `src/logic/gitignore-manager.ts`, `src/main.ts`, `styles.css`
- `tests/setup.ts`, `tests/ui/setup-dom.ts` (expanded Obsidian test mocks)
- `tests/logic/gitignore-manager.test.ts`, `tests/logic/sync-manager.test.ts`, `tests/logic/sync-manager-mapping.test.ts`
- `tests/ui/SyncConflictModal.test.ts`, `tests/ui/SettingsConnectionStatus.test.ts` (new)
- `src/services/git-service-base.ts`, `git-service-interface.ts`, `github-service.ts`, `gitlab-service.ts`, `gitea-service.ts` — tree-entry `sha`, `getBlob()`, HTML-error detection in `safeRequest`
- `src/logic/gitignore-manager.ts`, `src/ui/SyncStatusView.ts` — symlink-directory recursion guard, SHA-based status refresh, lazy diff loading
- `src/utils/symlink.ts` — Windows `symlinkSync` type hint
- `src/utils/git-blob-sha.ts`, `src/utils/version.ts` (new) — SHA-1 blob hashing, numeric version compare
- `src/changelog.ts`, `src/ui/WhatsNewModal.ts` (new) — what's-new modal + data
- `src/ui/components/DiffPanel.ts`, `FileListItem.ts` — on-demand diff fetch
- `src/settings.ts`, `src/main.ts``lastSeenVersion` field, update-check wiring
- Corresponding test files under `tests/` for all of the above (302 tests total)
## Decisions Made
- Bundled #40/#41/#42 into a single commit rather than three, since they touch overlapping files closely enough that atomic per-issue commits risked an intermediate broken build.
- Discarded the local uncommitted 1.12.x-compat WIP (user confirmed it was superseded by origin/main's PR #46) rather than trying to merge two different compat mechanisms.
- **One PR, not one-per-issue**: user said "不要那麼多pr merge" (don't want so many PRs). All further issue work goes directly onto `claude/fix-directory-symlink-pull-260713` — do not create a new branch/PR for the next issue.
- **`src/changelog.ts` is hand-curated, separate from `CHANGELOG.md`**: the auto-generated changelog (via semantic-release) isn't shipped in release assets and is too commit-log-granular for an end-user popup anyway.
- Deduped two accidental code-duplication regressions mid-session (test mocks, `getBlob` bodies) before they could trip SonarCloud's new-code gate again.
## Blockers / Risks
- None blocking. Open item: PR for the pushed branch hasn't been created — user was asked "要接著幫你開 PR 嗎?" and the session moved to harness setup before they answered.
- **#38 (i18n) needs a scope answer from the user before any code is written.** Options put to them: (a) build the i18n mechanism + fully migrate `settings.ts` only, Notices later; (b) fully migrate settings.ts *and* all 37 Notice call sites now; (c) build just the `t()`/detection/fallback mechanism with a couple of sample keys, defer actual string migration. Don't guess — the wrong choice means redoing a large diff.
- PR #51 is now fairly large (6 issues). Consider flagging to the user that it may be worth reviewing/merging before more commits pile on.
## Next Session Startup
1. Read `CLAUDE.md`.
2. Read `feature_list.json` and `progress.md`.
3. Review this handoff.
4. Run `./init.sh` before editing.
5. Check whether the user wants a PR opened for `claude/settings-ux-improvements-260713` before starting new work.
1. Read `CLAUDE.md`, `feature_list.json`, `progress.md`, then this file.
2. Run `./init.sh` before editing.
3. Ask the user for #38's scope decision (see "Blockers / Risks") before touching any i18n code.
4. All new commits go on `claude/fix-directory-symlink-pull-260713` (PR #51) unless told otherwise.
## Recommended Next Step
- Ask the user whether to open the PR for the pushed branch; if yes, use `gh pr create` per the repo's PR conventions (see `CLAUDE.md` / firstsun-pm workflow) and reference issues #40/#41/#42 so they auto-close on merge.
- Get the #38 scope decision, implement it, then move to #37 (Bitbucket provider) per the previously agreed order (39→38→37, all done).