Skip to content

Board/Calendar: preserve groupBy on fresh shard connect (closes #217) - #263

Merged
brylie merged 1 commit into
mainfrom
fix-217-board-groupby-fresh-connect
Sep 12, 2026
Merged

Board/Calendar: preserve groupBy on fresh shard connect (closes #217)#263
brylie merged 1 commit into
mainfrom
fix-217-board-groupby-fresh-connect

Conversation

@brylie

@brylie brylie commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #217: on fresh page load or reconnect, Board and Calendar view column/date grouping rendered completely empty even when config.groupBy was already valid and persisted.

Root Cause

When a collection_view block connects to its shard:

  1. useCollectionConnection initializes a new Y.Doc and WebsocketProvider.
  2. useCollectionView observes this doc and immediately triggers a synchronous refresh().
  3. Because the WebSocket connection has not yet synced data from the server, doc.getMap('collections') is empty (snapshot.collection === undefined, snapshot.schema === []).
  4. In BoardCollectionView and CalendarCollectionView, handleSnapshot was running without checking snapshot.collection:
    • It marked autoGroupByAttempted = true.
    • It ran autoPickGroupBy against the empty schema [].
    • autoPickGroupBy failed to find config.groupBy in the empty schema, resolved it to undefined, and called onConfigChange({ ...config, groupBy: undefined }).
    • This wiped the persisted groupBy setting, marked the draft dirty ("Unsaved changes"), and caused columns/dates to render empty (columns = []).
    • When the WebSocket sync subsequently arrived with the real schema, autoGroupByAttempted was already true, so handleSnapshot returned early without restoring the grouping property.
    • In addition, announcer.notify prematurely seeded its baseline against rows: [], causing existing records to be erroneously announced as newly added by remote collaborators.

Solution

  1. BoardCollectionView.svelte: Guard handleSnapshot with if (!snapshot.collection) return;.
  2. CalendarCollectionView.svelte: Guard handleSnapshot with if (!snapshot.collection) return;.
  3. TableCollectionView.svelte: Guard onSnapshot callback with if (!snapshot.collection) return; before calling announcer.notify.
  4. Regression Tests:
    • Added Tier B Playwright tests in tests/e2e/tier-b.spec.ts for Board fresh connect with persisted groupBy, Board auto-pick when unset, and Calendar fresh connect with persisted groupBy.
    • Added unit tests in BoardCollectionView.svelte.test.ts and CalendarCollectionView.svelte.test.ts verifying that when collection metadata arrives after initial mount, persisted groupBy is preserved.

Verification

  • npm run test:unit: All 1,279 unit tests pass.
  • npm run test:e2e:tier-b: All 9 Tier B Playwright tests pass.
  • npm run check: 0 errors, 0 warnings.
  • npm run lint: All checks pass.
  • scripts/pre-push-check.sh: Passed.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where Board, Calendar, and Table views could process incomplete initial data before synchronization finished.
    • Persisted grouping settings are now preserved when collections load after the view opens.
    • Prevented false activity announcements, automatic grouping changes, and incorrect unsaved-change indicators during initial synchronization.
  • Tests

    • Added coverage for delayed collection loading, persisted grouping preferences, and fresh-connection behavior across Board and Calendar views.

When a CollectionView block connects to its shard, useCollectionView
initially emits a synchronous snapshot before the WebSocket provider has
synced with the server, meaning snapshot.collection is undefined and
snapshot.schema/rows are empty.

In BoardCollectionView and CalendarCollectionView, handleSnapshot was
marking autoGroupByAttempted = true and executing autoPickGroupBy against
that empty schema. Because the schema was empty, autoPickGroupBy failed
to resolve the persisted groupBy property and reset it to undefined,
marking the draft dirty and causing Board columns and Calendar dates to
render completely empty. A subsequent real snapshot after sync was
ignored because autoGroupByAttempted had already been set. In addition,
announcer.notify was prematurely baselining against an empty list,
erroneously announcing existing records as newly added.

Guards handleSnapshot (and TableCollectionView's onSnapshot handler) to
return early if snapshot.collection is not yet populated. Adds unit and
Tier B Playwright regression coverage.
@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Essentials

Run ID: 2b7832f4-eb76-467d-b56d-1f06ed348279

📥 Commits

Reviewing files that changed from the base of the PR and between cd1c9b0 and 94e2c62.

📒 Files selected for processing (6)
  • src/lib/components/BoardCollectionView.svelte
  • src/lib/components/BoardCollectionView.svelte.test.ts
  • src/lib/components/CalendarCollectionView.svelte
  • src/lib/components/CalendarCollectionView.svelte.test.ts
  • src/lib/components/TableCollectionView.svelte
  • tests/e2e/tier-b.spec.ts

Included review availability: 3 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.


📝 Walkthrough

Walkthrough

Collection views now ignore initial snapshots without a collection. Board and Calendar views preserve persisted grouping, while Table avoids premature callbacks. Component and end-to-end tests cover delayed collection initialization and fresh-load grouping behavior.

Changes

Collection snapshot synchronization

Layer / File(s) Summary
Guard collection-less snapshots
src/lib/components/BoardCollectionView.svelte, src/lib/components/CalendarCollectionView.svelte, src/lib/components/TableCollectionView.svelte
Snapshot handlers return early when no collection exists, preventing premature grouping, announcements, and callbacks.
Validate delayed collection initialization
src/lib/components/BoardCollectionView.svelte.test.ts, src/lib/components/CalendarCollectionView.svelte.test.ts
Component tests verify that persisted groupBy values remain unchanged when collections arrive after initial render.
Validate fresh-load behavior
tests/e2e/tier-b.spec.ts
End-to-end tests cover persisted and automatic Board grouping and persisted Calendar date grouping after fresh loads without unsaved changes.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 94e2c

The fix preserves persisted grouping during initial synchronization and is covered by unit and end-to-end tests.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (3 skipped: 3 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving Board and Calendar groupBy settings during fresh shard connections. It also identifies the related issue.
Linked Issues check ✅ Passed Issue #217 requires Board grouping to survive the initial unsynchronized snapshot and render on a fresh load or reconnect. BoardCollectionView.svelte now ignores snapshots without a collection before …
Out of Scope Changes check ✅ Passed The changes are limited to the Board, Calendar, and Table snapshot handlers and their regression tests. The Board and Calendar changes directly address issue #217. The Table guard supports the issue's…
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 3 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-217-board-groupby-fresh-connect

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Preserve collection grouping across fresh shard connections

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Defers Board, Calendar, and Table snapshot side effects until collection metadata synchronizes.
• Preserves persisted grouping and prevents false remote-update announcements after fresh
 connections.
• Adds unit and browser regressions for delayed metadata, auto-picking, and persisted grouping.
Diagram

sequenceDiagram
    participant View as Collection View
    participant Conn as Shard Connection
    participant Doc as Y.Doc
    participant Sync as WebSocket Sync
    participant Handler as Snapshot Handler
    participant Effects as Config and Announcer
    View->>Conn: Connect to shard
    Conn->>Doc: Create local document
    Doc-->>Handler: Initial empty snapshot
    Handler-->>Handler: Skip missing collection
    Sync->>Doc: Apply synchronized data
    Doc-->>Handler: Populated snapshot
    Handler->>Effects: Preserve grouping and baseline rows
    Handler-->>View: Render grouped records
Loading
High-Level Assessment

The targeted consumer-side guards are appropriate because collection absence is the precise signal that synchronization is incomplete. Suppressing these snapshots centrally in useCollectionView was considered, but retaining the hook’s complete snapshot semantics avoids changing behavior for unrelated consumers while preventing destructive grouping and announcement side effects where necessary.

Files changed (6) +202 / -3

Bug fix (3) +11 / -0
BoardCollectionView.svelteIgnore unsynchronized Board snapshots +5/-0

Ignore unsynchronized Board snapshots

• Returns from the Board snapshot handler until collection metadata exists. This prevents persisted select grouping from being cleared and avoids baselining remote card announcements against empty rows.

src/lib/components/BoardCollectionView.svelte

CalendarCollectionView.svelteIgnore unsynchronized Calendar snapshots +5/-0

Ignore unsynchronized Calendar snapshots

• Defers Calendar grouping resolution and event announcements until collection metadata is available. Persisted date grouping therefore survives a fresh shard connection.

src/lib/components/CalendarCollectionView.svelte

TableCollectionView.svelteDelay Table snapshot side effects until synchronization +1/-0

Delay Table snapshot side effects until synchronization

• Skips announcement and external snapshot callbacks when collection metadata is absent. This prevents existing rows from being treated as newly added after the initial empty snapshot.

src/lib/components/TableCollectionView.svelte

Tests (3) +191 / -3
BoardCollectionView.svelte.test.tsTest delayed Board metadata synchronization +23/-0

Test delayed Board metadata synchronization

• Adds a regression test that mounts the Board before collection metadata arrives. It verifies persisted grouping remains intact and the expected column renders after synchronization.

src/lib/components/BoardCollectionView.svelte.test.ts

CalendarCollectionView.svelte.test.tsTest delayed Calendar metadata synchronization +25/-2

Test delayed Calendar metadata synchronization

• Extends the render helper to observe configuration changes and adds a delayed-metadata regression test. The test confirms the persisted date property remains selected after collection synchronization.

src/lib/components/CalendarCollectionView.svelte.test.ts

tier-b.spec.tsCover fresh-connect grouping in browser tests +143/-1

Cover fresh-connect grouping in browser tests

• Adds Playwright scenarios for persisted Board grouping, automatic Board grouping, and persisted Calendar date grouping on fresh connections. The tests also verify that valid persisted configuration does not produce an unsaved-changes state.

tests/e2e/tier-b.spec.ts

@qodo-code-review

qodo-code-review Bot commented Sep 12, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Screen readers retain stale updates 🐞 Bug ≡ Correctness
Description
handleSnapshot and Table's snapshot callback return before announcer.notify whenever collection
metadata is absent, so the announcer cannot clear its current text or reset its row baseline. When
an observed collection is deleted or a retargeted shard never yields metadata, Board, Calendar, and
Table empty their rendered data but leave the previous remote-update status and baseline attached to
the now-missing view.
Code

src/lib/components/BoardCollectionView.svelte[97]

+		if (!snapshot.collection) return;
Relevance

●●● Strong

Recent collection-view precedent accepts fixes preventing stale announcer baselines during
retargeting and reconnect transitions.

PR-#249

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
useCollectionView updates the rendered collection, schema, and rows before invoking the callback,
so a deleted collection already empties the visible view. The added early returns then skip the only
operation that clears announcer text and replaces its baseline; collection deletion removes metadata
and triggers precisely such a collection-less snapshot.

src/lib/client/collection-view.svelte.ts[57-76]
src/lib/client/collection-announcer.svelte.ts[129-140]
src/lib/client/collection-announcer.svelte.ts[143-162]
src/lib/data/collection-ops.ts[721-730]
src/lib/components/BoardCollectionView.svelte[92-98]
src/lib/components/CalendarCollectionView.svelte[87-93]
src/lib/components/TableCollectionView.svelte[90-97]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Collection-less snapshots must not be diffed as empty collections, but returning immediately preserves stale live-region text and the previous collection baseline when metadata genuinely disappears.

## Fix Focus Areas
- src/lib/components/BoardCollectionView.svelte[93-98]
- src/lib/components/CalendarCollectionView.svelte[88-93]
- src/lib/components/TableCollectionView.svelte[93-96]
- src/lib/client/collection-announcer.svelte.ts[125-140]

## Recommended Fix
Add an explicit announcer reset operation that clears text, baseline, collection identity, toggling state, and pending local removals without diffing rows. Invoke that reset for collection-less snapshots in all three views, then return before auto-grouping or external snapshot processing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Fresh-connect behavior is undocumented 📘 Rule violation § Compliance
Description
handleSnapshot and Table's snapshot callback now ignore snapshots where snapshot.collection is
absent, changing when grouping and remote-update baselines initialize. On a fresh or reconnecting
shard this defers initialization until synced metadata arrives across Board, Calendar, and Table,
but the collection-view and collaboration specifications do not define that transition.
Code

src/lib/components/BoardCollectionView.svelte[97]

+		if (!snapshot.collection) return;
Relevance

●●● Strong

Recent precedent accepts documenting externally observable behavior changes in markdown
specifications.

PR-#160

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Compliance rule 2945774 requires externally observable behavior changes to be reflected in the
corresponding markdown specification. The added guards in all three collection views defer
initialization during fresh shard connections, while the existing specifications describe
synchronous initial snapshots and first-snapshot announcement baselines without documenting that
collection-less snapshots are ignored.

Rule 2945774: Keep feature behavior in sync with docs/specifications markdown specs
src/lib/components/BoardCollectionView.svelte[93-97]
src/lib/components/CalendarCollectionView.svelte[88-92]
src/lib/components/TableCollectionView.svelte[94-95]
docs/specifications/collection-views.md[32-34]
docs/specifications/collaboration.md[19-26]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Board, Calendar, and Table now ignore the initial collection-less snapshot emitted before shard synchronization, but the corresponding specifications do not document when grouping and announcement baselines initialize.

## Fix Focus Areas
- docs/specifications/collection-views.md[32-34]
- docs/specifications/collaboration.md[19-26]

## Recommended Fix
Update the collection-view specification to state that snapshot consumers defer grouping initialization until collection metadata is available. Update the collaboration specification to clarify that collection-less pre-sync snapshots do not seed the remote-update baseline and that the first populated snapshot does.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 43 rules
Review mode: ⚖️ Balanced: This changes runtime connection/snapshot handling across Board, Calendar, and Table views plus end-to-end behavior, creating meaningful state and rendering regressions despite a focused fix.

Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

// WebSocket sync completes (snapshot.collection is undefined) — running
// autoPickGroupBy or announcer.notify against that empty doc would wipe
// an already-persisted groupBy and falsely baseline row diffs (issue #217).
if (!snapshot.collection) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Fresh-connect behavior is undocumented 📘 Rule violation § Compliance

handleSnapshot and Table's snapshot callback now ignore snapshots where snapshot.collection is
absent, changing when grouping and remote-update baselines initialize. On a fresh or reconnecting
shard this defers initialization until synced metadata arrives across Board, Calendar, and Table,
but the collection-view and collaboration specifications do not define that transition.
Agent Prompt
## Issue description
Board, Calendar, and Table now ignore the initial collection-less snapshot emitted before shard synchronization, but the corresponding specifications do not document when grouping and announcement baselines initialize.

## Fix Focus Areas
- docs/specifications/collection-views.md[32-34]
- docs/specifications/collaboration.md[19-26]

## Recommended Fix
Update the collection-view specification to state that snapshot consumers defer grouping initialization until collection metadata is available. Update the collaboration specification to clarify that collection-less pre-sync snapshots do not seed the remote-update baseline and that the first populated snapshot does.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

// WebSocket sync completes (snapshot.collection is undefined) — running
// autoPickGroupBy or announcer.notify against that empty doc would wipe
// an already-persisted groupBy and falsely baseline row diffs (issue #217).
if (!snapshot.collection) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Screen readers retain stale updates 🐞 Bug ≡ Correctness

handleSnapshot and Table's snapshot callback return before announcer.notify whenever collection
metadata is absent, so the announcer cannot clear its current text or reset its row baseline. When
an observed collection is deleted or a retargeted shard never yields metadata, Board, Calendar, and
Table empty their rendered data but leave the previous remote-update status and baseline attached to
the now-missing view.
Agent Prompt
## Issue description
Collection-less snapshots must not be diffed as empty collections, but returning immediately preserves stale live-region text and the previous collection baseline when metadata genuinely disappears.

## Fix Focus Areas
- src/lib/components/BoardCollectionView.svelte[93-98]
- src/lib/components/CalendarCollectionView.svelte[88-93]
- src/lib/components/TableCollectionView.svelte[93-96]
- src/lib/client/collection-announcer.svelte.ts[125-140]

## Recommended Fix
Add an explicit announcer reset operation that clears text, baseline, collection identity, toggling state, and pending local removals without diffing rows. Invoke that reset for collection-less snapshots in all three views, then return before auto-grouping or external snapshot processing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@brylie
brylie merged commit 895dc4b into main Sep 12, 2026
2 checks passed
@brylie
brylie deleted the fix-217-board-groupby-fresh-connect branch September 12, 2026 15:01
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.

Board view: groupBy columns render empty after a fresh connect until an unrelated interaction nudges reactivity

1 participant