Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cf2ccd0
fix(sync): preserve popover controls during status updates
elkimek Oct 31, 2026
88364ff
docs: specify stable sync status controls
elkimek Oct 31, 2026
bfb0426
test(sync): require settled replication on deployed relay fix
elkimek Oct 31, 2026
2f2e398
fix(sync): keep popover attribute names explicit for ownership audits
elkimek Oct 31, 2026
b8f460d
test: retain assertions across compressed sync and native Cashu amounts
elkimek Oct 31, 2026
0eeb8a5
test: qualify retained layout additions and reduce coverage harness c…
elkimek Oct 31, 2026
5602b95
fix: release optional startup reservations and complete coverage qual…
elkimek Oct 31, 2026
864bd02
Preserve frozen readiness infrastructure in release acceptance
elkimek Oct 31, 2026
4ca2408
Keep delayed Chat opening owned by its initiating profile
elkimek Oct 31, 2026
c4b475a
Retire interrupted visible Chat openings without closing newer requests
elkimek Oct 31, 2026
ef00b11
Wait for app readiness before offline Display interactions
elkimek Oct 31, 2026
5030d83
Qualify mobile startup guard and capture Cocoa Evolu install stalls
elkimek Oct 31, 2026
cad6fe9
Batch native coverage mapping without sharing execution credit
elkimek Oct 31, 2026
a74740c
fix: acknowledge backup recovery per profile to stop repeat uploads
elkimek Oct 31, 2026
1375d93
test: await stable relay storage after recovery observation
elkimek Oct 31, 2026
a132b16
fix: hand identity recovery to the reloaded owner runtime
elkimek Oct 31, 2026
6391006
fix: confirm recovery journal before rotating sync identity
elkimek Oct 31, 2026
84976d2
fix: roll back owned recovery intent after rejected rotation
elkimek Oct 31, 2026
47aa034
test: advance subscription retry clock within unchanged deadline
elkimek Oct 31, 2026
c279be4
test: align recovery fixtures and neutralize mobile tooltip pointer
elkimek Oct 31, 2026
9ec9e85
fix: bind identity recovery to its intended owner
elkimek Oct 31, 2026
988bb17
fix: finish identity recovery without redundant sync work
elkimek Oct 31, 2026
f124992
fix: stream coverage shards within the existing memory budget
elkimek Oct 31, 2026
77b76e9
Merge main backup compatibility fixes into sync recovery repair
elkimek Oct 31, 2026
6ef2b99
fix: release recovery for explicitly deleted profiles
elkimek Oct 31, 2026
48efd17
test: require forwarding database import options
elkimek Oct 31, 2026
3c43ed2
fix: cancel background Chat while foreground controls are active
elkimek Oct 31, 2026
08668b1
fix: retain saved edits across a same-profile reload
elkimek Oct 31, 2026
531e25f
test: set explicit analytics consent for authored workflows
elkimek Oct 31, 2026
ced5587
test: finish measurement migration before replacing its fixture
elkimek Oct 31, 2026
68f2291
test: locate offline context loss at save and manual sync boundaries
elkimek Oct 31, 2026
893c83d
fix: retire superseded profile pushes and callbacks
elkimek Oct 31, 2026
e035aa5
fix: settle superseded push status before releasing ownership
elkimek Oct 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/sync-compat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: elkimek/getbased-relay
ref: 2e7979ea5c72d0e6e0652b241d845c3b77762a24
ref: 4f44ebb32d8c44d29503cb075d57ce48a7519b28
path: .ci/getbased-relay
persist-credentials: false

Expand Down
86 changes: 80 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
type: choice
required: false
default: 'off'
options: ['off', 'webkit-lens-wearable', 'webkit-pwa-routstr']
options: ['off', 'webkit-lens-wearable', 'webkit-pwa-routstr', 'webkit-evolu-runtime']
workflow_call:
inputs:
full_suite:
Expand All @@ -25,14 +25,14 @@ on:
permissions:
contents: read
concurrency:
group: tests-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}-${{ inputs.full_suite == true && 'full' || 'scoped' }}${{ github.event_name == 'workflow_dispatch' && inputs.diagnostic == 'webkit-lens-wearable' && '-webkit-diagnostic' || '' }}${{ github.event_name == 'workflow_dispatch' && inputs.diagnostic == 'webkit-pwa-routstr' && '-webkit-pwa-routstr-diagnostic' || '' }}
group: tests-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}-${{ inputs.full_suite == true && 'full' || 'scoped' }}${{ github.event_name == 'workflow_dispatch' && inputs.diagnostic == 'webkit-lens-wearable' && '-webkit-diagnostic' || '' }}${{ github.event_name == 'workflow_dispatch' && inputs.diagnostic == 'webkit-pwa-routstr' && '-webkit-pwa-routstr-diagnostic' || '' }}${{ github.event_name == 'workflow_dispatch' && inputs.diagnostic == 'webkit-evolu-runtime' && '-webkit-evolu-diagnostic' || '' }}
cancel-in-progress: ${{ (github.event_name == 'pull_request' || github.event_name == 'push') && inputs.full_suite != true }}
env:
GETBASED_KIT_ARTIFACT: ${{ github.workspace }}/dist
CI_TEST_PLAN: .local-notes/ci/plan.json
jobs:
prepare:
if: github.event_name != 'workflow_dispatch' || (inputs.diagnostic != 'webkit-lens-wearable' && inputs.diagnostic != 'webkit-pwa-routstr')
if: github.event_name != 'workflow_dispatch' || (inputs.diagnostic != 'webkit-lens-wearable' && inputs.diagnostic != 'webkit-pwa-routstr' && inputs.diagnostic != 'webkit-evolu-runtime')
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
Expand Down Expand Up @@ -168,7 +168,9 @@ jobs:
- name: Full unit coverage and unchanged critical behavior floors
if: needs.prepare.outputs.full == 'true'
run: |
node node_modules/vitest/vitest.mjs run --coverage
# Leave room for native TypeScript and nested producer subprocesses.
# Keep the complete suite, coverage denominator and deadlines intact.
node node_modules/vitest/vitest.mjs run --coverage --maxWorkers=2
node node_modules/vitest/vitest.mjs run --config vitest.critical.config.js
- name: Affected unit tests and mandatory changed-source checks
if: needs.prepare.outputs.full != 'true'
Expand Down Expand Up @@ -516,9 +518,9 @@ jobs:
if-no-files-found: error
retention-days: 14
test:
name: ${{ github.event_name == 'workflow_dispatch' && (inputs.diagnostic == 'webkit-lens-wearable' || inputs.diagnostic == 'webkit-pwa-routstr') && 'DIAGNOSTIC normal gate omitted' || 'test' }}
name: ${{ github.event_name == 'workflow_dispatch' && (inputs.diagnostic == 'webkit-lens-wearable' || inputs.diagnostic == 'webkit-pwa-routstr' || inputs.diagnostic == 'webkit-evolu-runtime') && 'DIAGNOSTIC normal gate omitted' || 'test' }}
needs: [prepare, unit, native, kit, performance, coverage]
if: always() && (github.event_name != 'workflow_dispatch' || (inputs.diagnostic != 'webkit-lens-wearable' && inputs.diagnostic != 'webkit-pwa-routstr'))
if: always() && (github.event_name != 'workflow_dispatch' || (inputs.diagnostic != 'webkit-lens-wearable' && inputs.diagnostic != 'webkit-pwa-routstr' && inputs.diagnostic != 'webkit-evolu-runtime'))
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
Expand Down Expand Up @@ -782,3 +784,75 @@ jobs:
include-hidden-files: true
if-no-files-found: warn
retention-days: 7

webkit_evolu_diagnostics:
name: DIAGNOSTIC Cocoa Evolu restart (${{ matrix.project }}; not a release gate)
if: github.event_name == 'workflow_dispatch' && inputs.diagnostic == 'webkit-evolu-runtime'
runs-on: macos-15
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
project: [kit-webkit, kit-mobile-webkit]
env:
GETBASED_KIT_ACCEPTANCE_GUARD: '1'
GETBASED_KIT_GUARD_REPORT: .local-notes/ci/evolu-diagnostic/guard.json
PLAYWRIGHT_KIT_OUTPUT_DIR: .local-notes/ci/evolu-diagnostic/results
GETBASED_KIT_CASE_SCOPE: all
GETBASED_KIT_PROJECT: ${{ matrix.project }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
cache: npm
- run: node scripts/fetch-catalog.mts
- run: npm ci
- run: npx --no-install playwright install --with-deps webkit
- name: Build and verify the actual diagnostic artifact
run: |
set -euo pipefail
node scripts/build-sveltekit.mjs --output dist
node scripts/ci-artifact.mjs seal --artifact dist --transport .local-notes/ci/evolu-diagnostic/transport
node scripts/ci-artifact.mjs verify --artifact dist --transport .local-notes/ci/evolu-diagnostic/transport --github-env
- name: Run the canonical Evolu offline restart case without retries
id: diagnostic_case
env:
PROJECT: ${{ matrix.project }}
run: node node_modules/@playwright/test/cli.js test --config=playwright.sveltekit.config.js --project="$PROJECT" --workers=1 --retries=0 tests/pwa/sveltekit-evolu-runtime.spec.ts
- name: Require the exact successful diagnostic scope
if: always() && steps.diagnostic_case.outcome != 'skipped'
env:
PROJECT: ${{ matrix.project }}
run: |
node --input-type=module <<'NODE'
import assert from 'node:assert/strict';
import fs from 'node:fs';
const report = JSON.parse(fs.readFileSync(process.env.GETBASED_KIT_GUARD_REPORT, 'utf8'));
assert.equal(report.execution, true);
assert.equal(report.status, 'passed');
assert.deepEqual(report.issues, []);
assert.equal(report.tests.length, 1);
const test = report.tests[0];
assert.equal(test.file, 'tests/pwa/sveltekit-evolu-runtime.spec.ts');
assert.equal(test.project, process.env.PROJECT);
assert.equal(test.attemptCount, 1);
assert.equal(test.status, 'passed');
assert.equal(test.expectedStatus, 'passed');
assert.equal(test.declaredExpectedStatus, 'passed');
assert.equal(test.outcome, 'expected');
for (const annotations of [test.annotations, test.declaredAnnotations]) assert.equal(annotations.some(value => ['skip', 'fixme'].includes(value.type)), false);
NODE
- name: Label the focused evidence
if: always()
run: echo 'Focused Cocoa Evolu diagnostics only; the normal release matrix is still required.' >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
name: diagnostic-evolu-${{ matrix.project }}-${{ github.run_id }}
path: .local-notes/ci/evolu-diagnostic
include-hidden-files: true
if-no-files-found: error
retention-days: 7
57 changes: 56 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ checks use the fixed inventory in

## Presentation and lifecycle contract

Foreground modals and the Sync popover cancel optional Dashboard Chat startup,
including a request still awaiting module or presentation loading; explicit Chat
actions retain their normal behavior.
Optional empty-Dashboard Chat startup owns its reservation by render generation
and profile. Failed loads or a narrower viewport release it; completion from a
retired render cannot clear a newer reservation.
Lazy Chat open/toggle requests retain their initiating profile through module
loading. Panel opening checks that profile and its open intent after each
asynchronous load, so a deleted profile's pending request cannot initialize a
conversation on its temporary sync fallback or another selected profile.
An interrupted visible opening closes only while it still owns the latest open
intent, leaving a newer opening intact.

`body-measurement-model.ts` owns method-specific waist/hip/SAD field identities
and validated portable-mirror projection. `wearables-manual.ts` owns their paired
numeric writes and deletion clocks through the existing encrypted manual-reading
Expand Down Expand Up @@ -88,7 +101,9 @@ before dispatch, publication and persistence. Retired controls cannot start new
or update replacement views. Accepted domain writes can finish for their original
owner after closure; they must not publish into a new owner's UI or adopt unrelated
unsaved fields. Domain transactions, locks and explicit field baselines decide
commit/rollback, not component mount lifetime.
commit/rollback, not component mount lifetime. A save adopting into a replacement
object for the same profile uses that object's own read baseline, so stale loaded
values cannot undo the committed edit and newer unsaved fields remain intact.

Keep credentials and private metadata in dedicated live-field/service ports,
not general display snapshots or action attributes. Existing explicit Sync recovery
Expand Down Expand Up @@ -352,6 +367,14 @@ downgrade is not a database rollback: previous web runtimes cannot open every
new-format database. The v7 recovery path always retains its separate runtime and
database identity.

Overlapping forced pushes retain per-profile generations. A newer push supersedes
older asynchronous planning and completion; a newer saved edit invalidates an
older plan before its SDK write. Superseded snapshots cannot write stale deltas,
acknowledge a newer dirty marker or release another push's in-flight guard. A
superseded plan settles its status before releasing ownership and cannot hide
another profile's pending send. Manual sync retries the current snapshot within
its existing deadline.

V3 profile snapshots retain scalar values with per-field edit clocks, so an
unrelated save cannot give an unchanged scalar a newer clock. Full snapshots
protect peers whose profile and item-row query updates arrive separately.
Expand All @@ -363,13 +386,45 @@ insert contract. Never-configured absent fields do not create clear rows.
Legacy envelopes retain their profile-clock fallback. Explicit recovery also
re-emits scalar rows, including clears. Planning or row-mutation failures keep
local dirty markers queued and stop success confirmation.

Backup recovery acknowledges each committed profile independently before relay
tombstones can apply. Its journal stays readable by older tabs; generation,
owner and dirty-token checks prevent an old completion from acknowledging a
newer restore. Demo and explicitly deleted profiles are not republished.
Explicit local delete intent acknowledges recovery even after the profile leaves
the saved list; absence alone never discards a recovery entry.
Unavailable profiles retain their recovery protection without re-uploading
completed profiles. A failed progress write retains an in-memory commit receipt;
automatic failed publishes have a bounded retry budget, reset by an explicit
Sync now/resume or a newer local edit.
Identity rotation confirms a durable local recovery journal before changing the
owner. Failed writes/readback retain the original identity and saved Sync state.
If the SDK rejects a reset while its actual owner is unchanged, restore the exact
prior journal only while this operation still owns the staged generation. A
newer journal or an identity changed before startup failed retains its recovery.
The staged journal also binds to a domain-separated SHA-256 fingerprint of the
intended mnemonic; recovery words are never stored in the journal. Every
acknowledgement preserves that binding. A second tab with an older identity
defers recovery even while the originating tab is awaiting SDK reset.
Publishing stays suspended in the retiring runtime until replacement; the
reloaded owner publishes complete recovery. The old owner/query cache cannot
acknowledge the new owner's recovery. A deliberate join retargets existing
pending recovery before SDK reset, with the same owned rollback on failure.
Startup reconciliation may bypass the in-flight guard but retains the canonical
payload no-op check; an unchanged startup must not append relay history.
AI-setting retries stop after a successful unchanged receipt; unavailable
dirty-marker storage must not silently discard the requested check.
Profile metadata carries its own item row and durable edit clock; unrelated data rebroadcasts
retain it. Legacy metadata uses the envelope clock, and equal-clock metadata
conflicts resolve deterministically. Pulls defer pending local metadata edits
and ignore exact cached versions replaced by successful local pushes. Successful
recovery durably advances the local metadata clock; metadata edited while the
push is pending stays queued with a clock beyond that recovery.

Sync status emits only changed fields. An open status popover updates its
information in place, retaining action-button identity and keyboard focus so
replication activity cannot interrupt hover or a click in progress.

The static assembler externalizes Kit's classic bootstrap to satisfy the existing
CSP without permitting inline executable scripts. It copies only declared public
assets, retains `/`, `/index.html`, `/app` and `/app/`, and uses the existing
Expand Down
Loading
Loading