Skip to content

fix(shared-state): keep initial snapshots local - #454

Merged
antfu merged 5 commits into
devframes:mainfrom
SaKaNa-Y:fix/shared-state-initial-snapshot
Nov 2, 2026
Merged

antfu merged 5 commits into
devframes:mainfrom
SaKaNa-Y:fix/shared-state-initial-snapshot

Conversation

@SaKaNa-Y

@SaKaNa-Y SaKaNa-Y commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

When a wire service is installed while the RPC client reads its initial shared-state snapshot, the client can send the old snapshot back to the node side. This removes the service advertisement even though direct RPC calls still work. New connections then also fail to discover the installed service.

Give the initial snapshot its own sync ID and exclude that ID from outgoing writes. Track updates while the read is pending and ignore its result if the state changed or was deleted. This also preserves a local undo that restores the original object. Remove the temporary update listener when the read finishes. Local writes, including writes triggered by a snapshot listener, still reach the node side.

 initial snapshot arrives
- apply snapshot and send it back to the node side
+ apply locally only if no update occurred and the state still exists

This PR and #422 are independent fixes with no dependency on each other. This PR is based on main and does not include changes from #422. It fixes initial snapshot reads; #422 fixes echoes from received broadcasts and patches.

Evidence

  • The undo regression failed before the update: an old snapshot replaced the restored value. It passes with update tracking. A second test confirms that an unchanged mutation still allows the initial snapshot to apply.
  • All nine client shared-state tests and 16 shared-state utility tests pass. Coverage includes snapshot echo suppression, newer updates, local writes, undo, nested writes, and deletion.
  • Public WebSocket and SSE workflows pass against the updated build: early service installation preserves both registries, a new connection discovers the service, and direct RPC calls succeed.
  • pnpm lint, pnpm knip, pnpm typecheck, and pnpm build pass.
  • pnpm test --run: 157 test files pass; 1762 tests pass and 13 are skipped.

Merge Danger

Door: two-way. No wire-format or public API changes.

Blast Radius: shared-state. The change affects initial reads with an initialValue; later local writes retain their existing behavior.

@vercel

vercel Bot commented Oct 21, 2026

Copy link
Copy Markdown

@SaKaNa-Y is attempting to deploy a commit to the NuxtLabs Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

👁️‍🗨️ Review this pull request with grouped, summarized diffs at:
👉 https://pulls.review/gh/devframes/devframe/454?from=github-actions[bot]

raw result

💭 analyzed by vercel-ai-gateway/anthropic/claude-sonnet-5
🕰️ 2026-11-01 13:17 UTC
🔗 head bea3b05
🤖 automated by pulls.review

{
  "headSha": "bea3b054102507369c8e233ba2a3bccf22fdaaff",
  "result": {
    "overallSummary": "Fixes a race where an RPC client's initial shared-state snapshot read could resolve after a newer update/local write/delete and overwrite it, which could drop a service advertisement on the node side. The client now tags the initial read with its own sync ID, excludes it from outgoing writes, and only applies it if nothing changed in the meantime. An unrelated UnoCSS config fix pulled in from `main` is also included.",
    "groups": [
      {
        "key": "shared-state-fix",
        "label": "Keep initial snapshots local",
        "summary": "Gives each initial shared-state snapshot read its own sync ID, excludes it from outgoing writes, tracks updates/deletes that happen while the read is pending, and stops listening once the read resolves — preventing a stale snapshot from overwriting a newer local write or clobbering a service advertisement on the node side.",
        "category": "core",
        "filePaths": [
          "packages/devframe/src/client/rpc-shared-state.ts"
        ],
        "critical": true,
        "lineNotes": [
          {
            "path": "packages/devframe/src/client/rpc-shared-state.ts",
            "side": "additions",
            "line": 117,
            "text": "Core fix: the resolved snapshot is only applied if the state wasn't replaced/deleted and no update happened while the read was pending, otherwise the stale value would silently win."
          }
        ]
      },
      {
        "key": "shared-state-tests",
        "label": "Shared-state snapshot tests",
        "summary": "Adds coverage for the new initial-snapshot handling: no write-back, updates/local writes/undo during a pending read, unchanged mutations, writes triggered from a snapshot listener, and deletion before the snapshot resolves.",
        "category": "tests",
        "filePaths": [
          "packages/devframe/src/client/rpc-shared-state.test.ts"
        ]
      },
      {
        "key": "unocss-config-fix",
        "label": "Fix UnoCSS content exclude",
        "summary": "Unrelated fix (from a merged branch) restoring UnoCSS's default pipeline exclude, which `mergeConfigs` was otherwise wiping, causing no utilities to be extracted from app sources under UnoCSS 66.11.",
        "category": "build",
        "filePaths": [
          "design/uno.config.ts"
        ],
        "fileNotes": [
          {
            "path": "design/uno.config.ts",
            "text": "Merge artifact from `main`, unrelated to the shared-state fix but included in this PR's diff."
          }
        ]
      }
    ],
    "schemaVersion": 1,
    "source": "llm",
    "generatedAt": "2026-11-01T13:17:35.651Z",
    "model": "vercel-ai-gateway/anthropic/claude-sonnet-5",
    "locale": "en"
  }
}

@SaKaNa-Y
SaKaNa-Y marked this pull request as draft October 21, 2026 13:36
Track updates while the initial snapshot is pending. Restoring the original object after a local edit must not make a stale snapshot current again.

Add an undo regression and retain initial synchronization after unchanged mutations. All required checks and the WebSocket/SSE service-discovery workflows pass.
@SaKaNa-Y
SaKaNa-Y marked this pull request as ready for review November 1, 2026 05:04
antfubot and others added 2 commits November 1, 2026 13:01
`mergeConfigs` leaves `content.pipeline.exclude` as `[]`. UnoCSS 66.11
builds its exclude filter with an empty include list, so an empty
exclude matches every module and no utilities come from app sources.
The a11y panel lost `overflow-y-auto`, which broke the scroll e2e test.

Restate the default exclude in the shared design config so the merged
list is never empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antfu
antfu merged commit a96cda8 into devframes:main Nov 2, 2026
12 of 13 checks passed
@SaKaNa-Y
SaKaNa-Y deleted the fix/shared-state-initial-snapshot branch November 2, 2026 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants