Tail of #244. Three items:
1. Coverage-gate (main). develop had no coverage tooling at all. Added
@vitest/coverage-v8@4.1.6 (pinned to the vitest already in use) to the three
vitest packages — git-sync, editor-ext (which also gains its missing direct
`vitest` devDep), apps/client — and enabled v8 coverage with per-package
thresholds (no root vitest config exists, so per-package is the only
meaningful scope). v8 provider is chosen deliberately: istanbul broke on the
ESM `@docmost/editor-ext` barrel; v8 collects native runtime coverage and
never re-parses ESM. `enabled: true` wires the gate into the plain `test`
script, so `pnpm -r test` (the CI entrypoint) enforces it without a manual
`--coverage`. Thresholds set ~4-5 pts below measured current coverage so the
gate PASSES today and FAILS on regression (verified: forcing lines=95 on
editor-ext exits 1). `all: false` — coverage counts test-touched files;
documented in the configs (with `all: true` the many untested type/barrel
files would sink the % and make the gate meaningless).
Measured→threshold (S/B/F/L): git-sync 91.78/79.16/76.76/92.46 → 88/75/72/88;
editor-ext 58.58/48.1/64.96/58.91 → 54/44/60/54; client 59.93/58/48.47/59.39
→ 55/53/44/55. All exit 0.
2. acceptInvitation atomicity int-spec. New
apps/server/test/integration/workspace-accept-invitation-atomicity.int-spec.ts
(+ createDefaultGroup/createInvitation seeders in test/integration/db.ts per
its convention). Wires the real WorkspaceInvitationService with real
User/Group/GroupUser repos against the test Kysely, stubbing only the
post-commit collaborators. Asserts the invariant protected by
users_email_workspace_id_unique: (a) two CONCURRENT accepts → exactly one
fulfilled, one BadRequestException('Invitation already accepted'), membership
count == 1, invitation consumed; (b) repeated sequential accept → still one
membership; (c) the survivor is in the workspace default group (whole-tx, no
torn state). Ran against real Postgres+Redis: 3/3 pass.
3. turn-end decision unit test. `decideTurnEnd` does not exist as a symbol; the
turn-end logic lives in chat-thread.tsx's onFinish handler. Added a focused
block to the existing chat-thread.test.tsx (matching its hoisted-mock style):
clean finish → flush queued (continue); abort/disconnect/error → queue
preserved (end) with the correct notice; parent notified on every terminal
outcome. 8 passed (3 existing + 5 new).
Verified: git-sync 712, editor-ext 247, client 888 (all with the gate, exit 0);
int-spec 3/3 (real Postgres); tsc --noEmit clean for client + server;
pnpm install --frozen-lockfile consistent (lockfile additive).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
git-sync's converter-core (src/lib) was a byte-identical duplicate of the new
@docmost/prosemirror-markdown package (created in the previous commit). Switch
git-sync to consume the package and delete its copy — ending the duplication
that the whole #293 effort targets. Pure no-op: NO format/behavior change.
- git-sync depends on @docmost/prosemirror-markdown (workspace:*); engine
(stabilize/push/pull) + src/index barrel + 12 engine tests re-point their
converter imports to the package.
- Delete git-sync/src/lib (8 files) and the 23 duplicate converter-core test
files + their fixtures — the converter and its ~440 tests now live once, in the
package. git-sync keeps only its ENGINE tests, which exercise the converter
through the package (the no-op proof). Kept roundtrip-helpers.ts (an engine
test imports firstDivergence from it; pure helper, no double-run).
- Added docmostExtensions to the package barrel (a kept engine schema-validity
test needs it).
Verified: editor-ext + prosemirror-markdown + git-sync all tsc EXIT 0;
git-sync vitest 28 files, 268 passed, 0 failures (engine cycle/roundtrip/push/
pull/reconcile green = no-op proof); prosemirror-markdown vitest still 443 passed
| 1 expected-fail; pnpm --frozen-lockfile EXIT 0; no ../lib refs remain in git-sync.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR-A inherited a committed packages/git-sync/node_modules (31 files: pnpm store
symlinks, .bin shims, and a committed vitest .vite cache) that arrived in develop
with the dead build/ — the F2 junk class. The root .gitignore `/node_modules` is
anchored, so nested packages/*/node_modules slipped through.
- git rm --cached the 31 files.
- .gitignore: `/node_modules` -> `node_modules/` (non-anchored) so nested package
node_modules are ignored at any depth — closes the class, not just this instance.
- Add explicit "@docmost/editor-ext": "workspace:*" devDependency to git-sync
(schema-editor-ext-contract.test imported it via hoist; now declared).
Re-verified in a clean checkout (all from local store, no network):
pnpm install --frozen-lockfile EXIT 0; git-sync tsc EXIT 0; vitest 51 files,
711 passed | 1 expected-fail, 0 failures; schema-editor-ext-contract 2/2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The git-sync converter + engine source lived only on the #119 branch; develop
had just the dead compiled build/. Bring the whole package (src + ~700 tests)
onto develop under CI, with NO consumer wired — git-sync stays fully inert in
develop (nothing in apps/server imports it), so runtime behavior is unchanged.
This unblocks #293 (extract the shared converter package from the landed source)
and lets #119's functionality land LAST, already writing the canonical format
(per the #326 landing order).
- packages/git-sync: src (lib converter + engine) + test corpus + configs.
- Remove develop's dead committed packages/git-sync/build/; gitignore it
(built in CI/Docker via pnpm build, never committed — no src/build drift).
- pnpm-lock.yaml: add the @docmost/git-sync importer (a missing workspace
package in the lock is a CI blocker). `pnpm install --frozen-lockfile` passes.
- NO server integration / loader / Dockerfile runtime changes (those come with
#119 at step 6).
Verified: tsc clean; vitest 711 passed | 1 expected-fail, 0 failures, 0 type
errors; pnpm --frozen-lockfile EXIT 0; apps/server has no git-sync import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>