Skip to content

fix(pos): guard degraded storage mode#167

Open
kilbot wants to merge 2 commits into
mainfrom
codex/phase1-storage-health
Open

fix(pos): guard degraded storage mode#167
kilbot wants to merge 2 commits into
mainfrom
codex/phase1-storage-health

Conversation

@kilbot

@kilbot kilbot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • detect storage worker could not requestRemote failures as a first-class degraded storage state in the database layer
  • pause POS replications, surface a blocking storage-health banner, and disable checkout/save/void actions while storage is degraded
  • block remaining local write paths (barcode add, cart edits, coupon/fee/shipping adds, push document) and avoid false-success or duplicate error handling during degraded mode
  • add regression coverage for storage-health events, provider wiring, replication pause behavior, read-path worker failures, disabled POS action buttons, and degraded write guards

Test plan

  • Force a storage worker failure on a POS register and verify the storage-health banner appears and checkout/save/void actions are disabled.
  • Scan a barcode, edit a cart line, and try adding fee/shipping/coupon data while storage is degraded; verify the POS blocks the action instead of mutating the order or showing a false success.
  • Confirm active query/collection replications pause when storage health degrades and do not auto-restart until the app is recovered/reloaded.
  • Verify the wrapped storage layer marks degraded health for both bulkWrite and read-path worker failures (query / requestRemote) so the UI enters degraded mode consistently.
  • Run the targeted automated checks listed below:
    • pnpm jest packages/database/src/plugins/wrapped-error-handler-storage.test.ts --runInBand
    • npx jest --config jest.config.cjs tests/manager.test.ts tests/query-replication-state.test.ts --runInBand in packages/query
    • npx jest src/screens/main/contexts/storage-health/provider.test.tsx src/screens/main/pos/components/storage-health-banner.test.tsx src/screens/main/pos/cart/buttons/storage-health-guards.test.tsx src/screens/main/pos/hooks/use-add-item-to-order.test.ts src/screens/main/pos/hooks/use-update-line-item.test.ts src/screens/main/pos/hooks/use-add-fee.test.ts src/screens/main/pos/hooks/use-add-shipping.test.ts src/screens/main/pos/hooks/use-add-coupon.test.ts src/screens/main/contexts/use-push-document.test.ts src/screens/main/pos/products/use-barcode.test.ts --runInBand --collectCoverage=false in packages/core
    • pnpm run lint in packages/database, packages/query, packages/core, and apps/main

Closes #163

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Adds POS storage-health monitoring with an in-app banner alert when connectivity is degraded.
    • Disables Checkout, Save, and Void buttons while storage is degraded.
    • Temporarily disables barcode scanning during degraded storage.
    • Blocks order actions (add items, fees, shipping, coupons) while storage is degraded to protect data.
    • Pauses background synchronization/replication during degraded storage until health is restored.

@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a storage-health system: observable state in the database package, a StorageHealthProvider and hook, UI banner and guards that disable POS actions and barcode handling when storage is degraded, global replication pause/resume APIs, and related tests and translations.

Changes

Cohort / File(s) Summary
Storage Health Context & Utils
packages/core/src/screens/main/contexts/storage-health/provider.tsx, packages/core/src/screens/main/contexts/storage-health/error.ts, packages/core/src/screens/main/contexts/storage-health/provider.test.tsx
New StorageHealthProvider and useStorageHealth hook; create/isStorageDegradedError helpers; subscribes to storageHealth$ and pauses replications when status is degraded. Tests added.
Database Health Events & Wrappers
packages/database/src/plugins/storage-health-events.ts, packages/database/src/index.ts, packages/database/src/plugins/wrapped-error-handler-storage.ts, packages/database/src/plugins/wrapped-error-handler-storage.test.ts
New RxJS-backed storageHealth$ + snapshot and mutators (markStorageDegraded/markStorageHealthy/reset); wrapped storage error handler now marks degraded on worker communication failures and provides safe fallbacks. Re-exports added. Tests added.
Query Manager Replication Controls
packages/query/src/manager.ts, packages/query/src/query-replication-state.ts, packages/query/tests/*
Global pause/resume API added (pauseAllReplications/resumeAllReplications); replication start logic gated by pause reason; QueryReplicationState gains shouldRestartCollectionReplication hook. Tests added for pause behavior.
POS UI: Banner & Translations
packages/core/src/screens/main/pos/components/storage-health-banner.tsx, packages/core/src/screens/main/pos/components/storage-health-banner.test.tsx, packages/core/src/contexts/translations/locales/en/core.json
StorageHealthBanner component displays two translated warning lines when degraded; three new English translation keys added. Tests added.
POS Button Guards
packages/core/src/screens/main/pos/cart/buttons/pay.tsx, packages/core/src/screens/main/pos/cart/buttons/save-order.tsx, packages/core/src/screens/main/pos/cart/buttons/void.tsx, packages/core/src/screens/main/pos/cart/buttons/storage-health-guards.test.tsx
Buttons check isDegraded, disable when degraded, and short-circuit degraded-storage errors in handlers. Tests assert disabled state under degraded conditions.
POS Hooks: Early-exit on Degraded Storage
packages/core/src/screens/main/pos/hooks/... (e.g. use-add-item-to-order.ts, use-add-product.ts, use-add-variation.ts, use-add-fee.ts, use-add-shipping.ts, use-update-line-item.ts, use-add-coupon.ts, plus many corresponding *.test.ts)
Multiple POS hooks now check storage health (useStorageHealth / throwIfStorageDegraded) and either throw or return early when degraded; catch paths treat degraded errors specially to suppress non-actionable logging. Tests added/updated.
Push & Barcode Flows
packages/core/src/screens/main/contexts/use-push-document.ts, packages/core/src/screens/main/pos/products/use-barcode.ts, packages/core/src/screens/main/contexts/use-push-document.test.ts, packages/core/src/screens/main/pos/products/use-barcode.test.ts
Document push and barcode handling short-circuit when storage is degraded; logs/toasts are recorded and operations are prevented. Tests added.
App Layout Integration
apps/main/app/(app)/_layout.tsx, apps/main/app/(app)/(drawer)/(pos)/_layout.tsx
StorageHealthProvider added to app layout; StorageHealthBanner mounted inside POS stack so banner is shown when degraded.

Sequence Diagram(s)

sequenceDiagram
    actor Staff as Staff (POS Operator)
    participant UI as POS UI
    participant Provider as StorageHealthProvider
    participant DB as Database/Worker
    participant RepMgr as Replication Manager
    participant Handler as Wrapped Error Handler

    Staff->>UI: Scan barcode / press checkout
    UI->>Provider: check isDegraded?
    opt worker communication fails
      DB->>Handler: bulkWrite/query error ("could not requestRemote")
      Handler->>Handler: classify error (WORKER_CONNECTION_LOST)
      Handler->>DB: markStorageDegraded(source, reason)
      DB->>Provider: emit degraded via storageHealth$
      Provider->>RepMgr: pauseAllReplications("storage-health")
    end
    Provider->>UI: isDegraded = true
    UI-->>Staff: disable buttons, skip barcode search, show StorageHealthBanner
    Staff->>UI: reload/recover
    UI->>DB: recovery actions
    DB->>Provider: markStorageHealthy()
    Provider->>RepMgr: resumeAllReplications()
    Provider->>UI: isDegraded = false (banner cleared, buttons enabled)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

bug, enhancement, priority/P1

Poem

🐰 When the worker hiccups and writes fall slow,
I thump my foot and say "pause" — don't let orders go,
A banner hops up, the buttons go gray,
Reload, resume, and we’ll bounce back today! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. 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 PR title 'fix(pos): guard degraded storage mode' accurately and concisely describes the main change—adding guards to prevent POS operations during storage degradation.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #163: detects storage worker failures as degraded state, pauses replications, surfaces a blocking banner, disables checkout/save/void, and blocks local write paths.
Out of Scope Changes check ✅ Passed All changes are scoped to addressing storage health degradation in the POS: error detection, provider wiring, UI guards, action button disabling, and write-path blocking—with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/phase1-storage-health

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/database/src/plugins/wrapped-error-handler-storage.ts (1)

58-69: ⚠️ Potential issue | 🟠 Major

Normalize worker-connection errors before rethrow to match degraded guards.

This path marks degraded state, but it rethrows the original error unchanged. Downstream degraded checks in packages/core/src/screens/main/contexts/storage-health/error.ts only match error.code === WORKER_CONNECTION_LOST or message 'storage unavailable', so raw 'could not requestRemote' errors can bypass degraded-mode short-circuit handling.

🛠️ Proposed fix
 	// Worker communication failures
 	if (message.includes('could not requestRemote')) {
+		if (error instanceof Error) {
+			(error as Error & { code?: string }).code = ERROR_CODES.WORKER_CONNECTION_LOST;
+		}
 		markStorageDegraded(methodName, message);
 		storageLogger.error(`Storage worker error in ${methodName}`, {
 			saveToDb: true,
 			context: {
 				errorCode: ERROR_CODES.WORKER_CONNECTION_LOST,
 				method: methodName,
 			},
 		});
 		// Don't suppress -- this is critical
 		return false;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/database/src/plugins/wrapped-error-handler-storage.ts` around lines
58 - 69, The handler currently marks storage degraded for "could not
requestRemote" but rethrows the original error, so downstream guards (which
check error.code === WORKER_CONNECTION_LOST or message === 'storage
unavailable') miss it; update the branch in wrapped-error-handler-storage (the
block using methodName, markStorageDegraded and storageLogger) to normalize the
thrown error by either setting err.code = ERROR_CODES.WORKER_CONNECTION_LOST
and/or replacing the message with 'storage unavailable' (or throw a new Error
object with those properties) before rethrowing so downstream checks in
storage-health/error.ts will match.
🧹 Nitpick comments (5)
packages/core/src/screens/main/pos/hooks/use-update-line-item.test.ts (1)

108-117: Add degraded-path coverage for splitLineItem too.

This hook exposes two mutation paths (updateLineItem, splitLineItem), but only one degraded guard is asserted here. A matching splitLineItem degraded test would prevent regressions on the second write path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/screens/main/pos/hooks/use-update-line-item.test.ts` around
lines 108 - 117, Add a sibling test to the existing degraded-storage case that
asserts the hook's splitLineItem path is also blocked: when mockUseStorageHealth
returns { status: 'degraded', isDegraded: true }, render the hook via
renderHook(() => useUpdateLineItem()) and call result.current.splitLineItem(...)
expecting it to reject with 'storage unavailable' and verifying mockLocalPatch
(or other write mocks) was not called; mirror the structure of the
updateLineItem test so splitLineItem's degraded-guard cannot regress.
packages/database/src/plugins/storage-health-events.ts (1)

33-39: Small DRY cleanup opportunity in health reset helpers.

resetStorageHealth() can delegate to markStorageHealthy() so healthy-state reset logic stays in one place.

♻️ Proposed refactor
 export function markStorageHealthy() {
 	subject.next(initialState);
 }
 
 export function resetStorageHealth() {
-	subject.next(initialState);
+	markStorageHealthy();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/database/src/plugins/storage-health-events.ts` around lines 33 - 39,
resetStorageHealth duplicates the same logic as markStorageHealthy; update
resetStorageHealth to delegate to markStorageHealthy instead of calling
subject.next(initialState) directly so the healthy-state reset logic is
centralized in markStorageHealthy (reference functions: markStorageHealthy,
resetStorageHealth, and subject/initialState).
packages/query/tests/manager.test.ts (1)

219-239: Good baseline test; consider adding a paused-registration assertion.

This covers pausing active replications well. To protect the new start-gating path, also assert that a query registered after pauseAllReplications() does not auto-start replication until resume.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query/tests/manager.test.ts` around lines 219 - 239, Add an
assertion to the test that registering a query after calling
manager.pauseAllReplications('storage-health') does not auto-start replication:
after calling manager.pauseAllReplications, register another query via
manager.registerQuery(...) (use same or new queryKeys like
'products-health-late'), then locate the new replication in
manager.activeCollectionReplications and/or manager.activeQueryReplications and
assert its subjects.paused.getValue() is true (and/or that its start() was not
invoked/replication not running) to verify the start-gating behavior; reference
manager.registerQuery, manager.pauseAllReplications,
manager.activeCollectionReplications, manager.activeQueryReplications, and
subjects.paused in your checks.
packages/database/src/plugins/wrapped-error-handler-storage.test.ts (1)

216-237: Harden subscription cleanup in degraded-health tests.

If an assertion fails before Line 236/Line 375, the subscription is left active and can bleed state into later tests. Wrap the assertions in try/finally to always unsubscribe.

♻️ Suggested test-hardening pattern
- const sub = storageHealth$.subscribe((state) => healthStates.push(state.status));
- await expect(wrappedInstance.query({} as any)).rejects.toThrow('could not requestRemote');
- expect(healthStates).toContain('degraded');
- sub.unsubscribe();
+ const sub = storageHealth$.subscribe((state) => healthStates.push(state.status));
+ try {
+   await expect(wrappedInstance.query({} as any)).rejects.toThrow('could not requestRemote');
+   expect(healthStates).toContain('degraded');
+ } finally {
+   sub.unsubscribe();
+ }

Also applies to: 357-376

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/database/src/plugins/wrapped-error-handler-storage.test.ts` around
lines 216 - 237, The test "emits degraded health when query loses worker
connection" creates a subscription (healthStates via storageHealth$.subscribe
assigned to sub) but doesn't guarantee cleanup on assertion failures; update the
test (and the similar one around lines 357-376) to wrap the assertions and
expectations in a try/finally so that sub.unsubscribe() is always called in the
finally block, ensuring the subscription is cleaned up even if
wrappedInstance.query or expectations throw.
packages/core/src/screens/main/pos/components/storage-health-banner.tsx (1)

19-29: Consider adding accessibility semantics for this blocking warning.

Since this is a critical operational warning, adding alert semantics (or equivalent RN a11y props) would improve assistive-tech visibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/screens/main/pos/components/storage-health-banner.tsx`
around lines 19 - 29, The storage-health-banner element (testID
'storage-health-banner' in the storage-health-banner component) needs explicit
accessibility semantics for a blocking alert; update the root element (where
testID: 'storage-health-banner' is set) to include RN a11y props such as
accessibilityRole="alert" (or accessibilityRole="header" +
accessibilityLiveRegion="assertive"/accessibilityLiveRegion on platforms that
support it), importantForAccessibility="yes" (or "yes-hide-descendants" as
appropriate), and a clear accessibilityLabel that includes the localized message
(e.g., combine t('common.pos_storage_connection_lost') and
t('common.stop_using_this_register_until_reloaded')); ensure these props are
applied on the same React element that renders the banner so assistive tech will
announce it immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/screens/main/contexts/storage-health/error.ts`:
- Around line 10-15: isStorageDegradedError currently only checks
ERROR_CODES.WORKER_CONNECTION_LOST and exact message 'storage unavailable',
which misses worker-connection failures rethrown as messages like "could not
requestRemote"; update isStorageDegradedError to also detect these raw worker
connection failures by checking error instanceof Error and whether error.message
contains a worker-connection indicator (e.g. includes 'could not requestRemote'
or other known worker connection substrings) in addition to the existing code
check (use the same error cast pattern Error & { code?: string } and referenced
ERROR_CODES.WORKER_CONNECTION_LOST).

In `@packages/core/src/screens/main/pos/hooks/use-update-line-item.test.ts`:
- Around line 112-115: The test's exact-string assertion is brittle; change the
assertion for result.current.updateLineItem(...) so it doesn't rely on the
literal 'storage unavailable' string. Instead reject and capture the error and
assert its message includes the translation value produced by the mocked useT
(e.g., check error.message includes t('common.pos_storage_connection_lost')) or
simply use expect(...).rejects.toThrow() plus a separate
expect(error.message).toContain(t('common.pos_storage_connection_lost')); locate
this in the use-update-line-item.test.ts where updateLineItem is called and
adjust the assertion accordingly.

---

Outside diff comments:
In `@packages/database/src/plugins/wrapped-error-handler-storage.ts`:
- Around line 58-69: The handler currently marks storage degraded for "could not
requestRemote" but rethrows the original error, so downstream guards (which
check error.code === WORKER_CONNECTION_LOST or message === 'storage
unavailable') miss it; update the branch in wrapped-error-handler-storage (the
block using methodName, markStorageDegraded and storageLogger) to normalize the
thrown error by either setting err.code = ERROR_CODES.WORKER_CONNECTION_LOST
and/or replacing the message with 'storage unavailable' (or throw a new Error
object with those properties) before rethrowing so downstream checks in
storage-health/error.ts will match.

---

Nitpick comments:
In `@packages/core/src/screens/main/pos/components/storage-health-banner.tsx`:
- Around line 19-29: The storage-health-banner element (testID
'storage-health-banner' in the storage-health-banner component) needs explicit
accessibility semantics for a blocking alert; update the root element (where
testID: 'storage-health-banner' is set) to include RN a11y props such as
accessibilityRole="alert" (or accessibilityRole="header" +
accessibilityLiveRegion="assertive"/accessibilityLiveRegion on platforms that
support it), importantForAccessibility="yes" (or "yes-hide-descendants" as
appropriate), and a clear accessibilityLabel that includes the localized message
(e.g., combine t('common.pos_storage_connection_lost') and
t('common.stop_using_this_register_until_reloaded')); ensure these props are
applied on the same React element that renders the banner so assistive tech will
announce it immediately.

In `@packages/core/src/screens/main/pos/hooks/use-update-line-item.test.ts`:
- Around line 108-117: Add a sibling test to the existing degraded-storage case
that asserts the hook's splitLineItem path is also blocked: when
mockUseStorageHealth returns { status: 'degraded', isDegraded: true }, render
the hook via renderHook(() => useUpdateLineItem()) and call
result.current.splitLineItem(...) expecting it to reject with 'storage
unavailable' and verifying mockLocalPatch (or other write mocks) was not called;
mirror the structure of the updateLineItem test so splitLineItem's
degraded-guard cannot regress.

In `@packages/database/src/plugins/storage-health-events.ts`:
- Around line 33-39: resetStorageHealth duplicates the same logic as
markStorageHealthy; update resetStorageHealth to delegate to markStorageHealthy
instead of calling subject.next(initialState) directly so the healthy-state
reset logic is centralized in markStorageHealthy (reference functions:
markStorageHealthy, resetStorageHealth, and subject/initialState).

In `@packages/database/src/plugins/wrapped-error-handler-storage.test.ts`:
- Around line 216-237: The test "emits degraded health when query loses worker
connection" creates a subscription (healthStates via storageHealth$.subscribe
assigned to sub) but doesn't guarantee cleanup on assertion failures; update the
test (and the similar one around lines 357-376) to wrap the assertions and
expectations in a try/finally so that sub.unsubscribe() is always called in the
finally block, ensuring the subscription is cleaned up even if
wrappedInstance.query or expectations throw.

In `@packages/query/tests/manager.test.ts`:
- Around line 219-239: Add an assertion to the test that registering a query
after calling manager.pauseAllReplications('storage-health') does not auto-start
replication: after calling manager.pauseAllReplications, register another query
via manager.registerQuery(...) (use same or new queryKeys like
'products-health-late'), then locate the new replication in
manager.activeCollectionReplications and/or manager.activeQueryReplications and
assert its subjects.paused.getValue() is true (and/or that its start() was not
invoked/replication not running) to verify the start-gating behavior; reference
manager.registerQuery, manager.pauseAllReplications,
manager.activeCollectionReplications, manager.activeQueryReplications, and
subjects.paused in your checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43aa2876-d918-4fde-ac13-4b5c09a1ce65

📥 Commits

Reviewing files that changed from the base of the PR and between 1b49afc and 34d7820.

📒 Files selected for processing (37)
  • apps/main/app/(app)/(drawer)/(pos)/_layout.tsx
  • apps/main/app/(app)/_layout.tsx
  • packages/core/src/contexts/translations/locales/en/core.json
  • packages/core/src/screens/main/contexts/storage-health/error.ts
  • packages/core/src/screens/main/contexts/storage-health/provider.test.tsx
  • packages/core/src/screens/main/contexts/storage-health/provider.tsx
  • packages/core/src/screens/main/contexts/use-push-document.test.ts
  • packages/core/src/screens/main/contexts/use-push-document.ts
  • packages/core/src/screens/main/pos/cart/buttons/pay.tsx
  • packages/core/src/screens/main/pos/cart/buttons/save-order.tsx
  • packages/core/src/screens/main/pos/cart/buttons/storage-health-guards.test.tsx
  • packages/core/src/screens/main/pos/cart/buttons/void.tsx
  • packages/core/src/screens/main/pos/components/storage-health-banner.test.tsx
  • packages/core/src/screens/main/pos/components/storage-health-banner.tsx
  • packages/core/src/screens/main/pos/hooks/block-on-storage-degraded.ts
  • packages/core/src/screens/main/pos/hooks/use-add-coupon.test.ts
  • packages/core/src/screens/main/pos/hooks/use-add-coupon.ts
  • packages/core/src/screens/main/pos/hooks/use-add-fee.test.ts
  • packages/core/src/screens/main/pos/hooks/use-add-fee.ts
  • packages/core/src/screens/main/pos/hooks/use-add-item-to-order.test.ts
  • packages/core/src/screens/main/pos/hooks/use-add-item-to-order.ts
  • packages/core/src/screens/main/pos/hooks/use-add-product.ts
  • packages/core/src/screens/main/pos/hooks/use-add-shipping.test.ts
  • packages/core/src/screens/main/pos/hooks/use-add-shipping.ts
  • packages/core/src/screens/main/pos/hooks/use-add-variation.ts
  • packages/core/src/screens/main/pos/hooks/use-update-line-item.test.ts
  • packages/core/src/screens/main/pos/hooks/use-update-line-item.ts
  • packages/core/src/screens/main/pos/products/use-barcode.test.ts
  • packages/core/src/screens/main/pos/products/use-barcode.ts
  • packages/database/src/index.ts
  • packages/database/src/plugins/storage-health-events.ts
  • packages/database/src/plugins/wrapped-error-handler-storage.test.ts
  • packages/database/src/plugins/wrapped-error-handler-storage.ts
  • packages/query/src/manager.ts
  • packages/query/src/query-replication-state.ts
  • packages/query/tests/manager.test.ts
  • packages/query/tests/query-replication-state.test.ts

Comment thread packages/core/src/screens/main/contexts/storage-health/error.ts
@github-actions

github-actions Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

📊 Test Coverage Report

Package Statements Branches Functions Lines
@wcpos/core 🔴 29.9% 🔴 29.4% 🔴 33.7% 🔴 29.5%
@wcpos/components 🔴 56.4% 🟡 73.6% 🔴 34.4% 🔴 58.9%
@wcpos/database 🔴 41.4% 🔴 46.1% 🔴 48.2% 🔴 40.5%
@wcpos/hooks 🔴 59.0% 🔴 55.4% 🔴 53.1% 🔴 58.9%
@wcpos/utils 🔴 55.9% 🟡 61.5% 🟡 71.4% 🔴 53.8%
@wcpos/query 🟡 79.6% 🟡 70.6% 🟡 73.8% 🟡 79.7%
Average 🔴 53.7% 🔴 56.1% 🔴 52.4% 🔴 53.6%
Coverage Legend
  • 🟢 Good (≥80%)
  • 🟡 Moderate (60-79%)
  • 🔴 Needs improvement (<60%)
  • ⚪ No coverage data
--- 🤖 Updated by GitHub Actions • [View full report](https://github.com/wcpos/monorepo/actions/runs/22777835961)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34d7820aaa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/screens/main/contexts/storage-health/error.ts Outdated
@github-actions

github-actions Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployment Summary

Item Status
Preview URL https://wcpos--2c4qb8lo9k.expo.app
E2E Tests ✅ Passed
Commit 440daa4

🔗 Quick Links


🤖 Updated by GitHub Actions

@wcpos-bot

wcpos-bot Bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Fix Summary — Round 1

# Source File Category Decision Outcome
1 CodeRabbit error.ts Important Fixed Added 'could not requestRemote' and StorageDegradedError name detection to isStorageDegradedError
2 CodeRabbit use-update-line-item.test.ts Minor Declined The thrown error comes from createStorageDegradedError() with hardcoded 'storage unavailable' message — the suggested 'common.pos_storage_connection_lost' would break the test since that i18n key is only used for the logger toast, not the thrown error
3 Codex error.ts Important Fixed Same concern as #1, addressed by same fix
4 CodeRabbit (body) wrapped-error-handler-storage.ts Important Fixed Normalized rethrown error with error.code = WORKER_CONNECTION_LOST
5 CodeRabbit (nitpick) storage-health-events.ts Minor Fixed resetStorageHealth() now delegates to markStorageHealthy()
6 CodeRabbit (nitpick) wrapped-error-handler-storage.test.ts Minor Fixed Hardened subscription cleanup with try/finally in both degraded-health tests

Commit: 2dd77a9
Fixed: 5 | Declined: 1 | Deferred: 0

- Expand isStorageDegradedError to detect raw 'could not requestRemote'
  worker-connection failures, not just synthetic StorageDegradedError
- Normalize rethrown worker errors by stamping error.code with
  WORKER_CONNECTION_LOST before rethrow in wrapped-error-handler-storage
- DRY: resetStorageHealth delegates to markStorageHealthy
- Harden subscription cleanup in degraded-health tests with try/finally

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/database/src/plugins/storage-health-events.ts`:
- Around line 20-22: getStorageHealthSnapshot currently returns the internal
StorageHealthState by reference and the code re-emits the shared initialState
object, which allows callers to mutate global state; change
getStorageHealthSnapshot to return a defensive copy of subject.getValue() (e.g.,
structuredClone or a deep-clone helper) and ensure any code that emits or resets
to initialState (the place that re-emits initialState) also emits a cloned copy
rather than the original initialState object so the subject always holds an
immutable/internal instance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f99443b-b7b1-4f58-ac98-365c210f6ef3

📥 Commits

Reviewing files that changed from the base of the PR and between 34d7820 and 2dd77a9.

📒 Files selected for processing (4)
  • packages/core/src/screens/main/contexts/storage-health/error.ts
  • packages/database/src/plugins/storage-health-events.ts
  • packages/database/src/plugins/wrapped-error-handler-storage.test.ts
  • packages/database/src/plugins/wrapped-error-handler-storage.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/database/src/plugins/wrapped-error-handler-storage.test.ts
  • packages/core/src/screens/main/contexts/storage-health/error.ts
  • packages/database/src/plugins/wrapped-error-handler-storage.ts

Comment on lines +20 to +22
export function getStorageHealthSnapshot(): StorageHealthState {
return subject.getValue();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid exposing and reusing mutable state objects.

Line 21 returns the internal state object by reference, and Line 34 re-emits a shared initialState object. A caller can accidentally mutate that object and silently corrupt global health state (including future resets).

Suggested fix
 export interface StorageHealthState {
-	status: StorageHealthStatus;
-	reason?: string;
-	source?: string;
-	lastErrorAt?: number;
+	readonly status: StorageHealthStatus;
+	readonly reason?: string;
+	readonly source?: string;
+	readonly lastErrorAt?: number;
 }
 
-const initialState: StorageHealthState = {
-	status: 'healthy',
-};
+function createHealthyState(): StorageHealthState {
+	return { status: 'healthy' };
+}
 
-const subject = new BehaviorSubject<StorageHealthState>(initialState);
+const subject = new BehaviorSubject<StorageHealthState>(createHealthyState());
@@
 export function getStorageHealthSnapshot(): StorageHealthState {
-	return subject.getValue();
+	return { ...subject.getValue() };
 }
@@
 export function markStorageHealthy() {
-	subject.next(initialState);
+	subject.next(createHealthyState());
 }

Also applies to: 33-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/database/src/plugins/storage-health-events.ts` around lines 20 - 22,
getStorageHealthSnapshot currently returns the internal StorageHealthState by
reference and the code re-emits the shared initialState object, which allows
callers to mutate global state; change getStorageHealthSnapshot to return a
defensive copy of subject.getValue() (e.g., structuredClone or a deep-clone
helper) and ensure any code that emits or resets to initialState (the place that
re-emits initialState) also emits a cloned copy rather than the original
initialState object so the subject always holds an immutable/internal instance.

@wcpos-bot

wcpos-bot Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

⚠️ Merge conflicts detected. This PR is 23 days old and needs a rebase against main before it can be merged.

@wcpos-bot

wcpos-bot Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

@kilbot: This PR is unassigned and has been open since March 6th. Please assign yourself or ping the relevant team for review/assignment. Adding 'priority/P2' and 'bug' labels based on the summary.

@wcpos-bot wcpos-bot Bot mentioned this pull request Apr 8, 2026
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.

POS stops reading SKU/barcodes when could not requestRemote / bulkWrite sync error appears

1 participant