Skip to content

fix(service): My Open Cases lists open cases, not merely unclosed ones - #1539

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-1328-is-closed-consumers
Sep 3, 2026
Merged

fix(service): My Open Cases lists open cases, not merely unclosed ones#1539
os-sales merged 1 commit into
mainfrom
claude/issue-1328-is-closed-consumers

Conversation

@os-sales

@os-sales os-sales commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1328

Description

Executes ruling B on the decision card (2026-08-31): move my_open_cases onto the live-work status predicate, keep both critical-case sharing rules and write down why, and reword the view's description in all three doc faces.

A tab labelled My Open Cases filtered on is_closed, which case_sla_defaults derives as status === 'closed' — it never flips on resolved. So an agent's "open" queue carried their finished work as well as their live work. It now states the same predicate the other live-work consumers already use: status not_in ['resolved', 'closed'].

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Documentation update

Related Issues

Ruling and parity guard: #1145 (PR #1323). Boundary-roster precedent: #1325. Neither is touched here.

Changes Made

  • src/views/case.view.tsmy_open_cases.filter moves from is_closed equals false to status not_in ['resolved','closed'], spelled the way the neighbouring sla_at_risk and unassigned_triage views already spell it, with the reasoning beside it.
  • src/sharing/case.sharing.tscomments only, zero behaviour change (the diff is 56 added lines, all of them comment lines; both condition: lines are byte-identical). Each of case_escalation_sharing and case_director_sharing now carries a block saying the standing access to resolved critical cases is deliberate, why, and what would have to become true for it to be wrong.
  • test/live-work-predicate-parity.test.tsmy_open_cases moves from the NOT_LIVE_WORK boundary roster into LIVE_WORK_CONSUMERS, and joins the is_closed offender sweep. The two sharing rules stay on the boundary roster, their why rewritten from "out of scope by A resolved unowned case sits in Unassigned — triage forever: the tab filters is_closed, which only flips on closed #1145" to the ruled keep and its conditions.
  • test/case-create-form-narrowing.test.ts — the is_closed retention entry drops my_open_cases.filter and keeps case_workflow.filter. See Bounded in-place fix below.
  • content/docs/service/cases.mdx, .zh-Hans.mdx, .zh-Hant.mdx — the views table row for My Open Cases, all three faces, line 181 in each.
  • .changeset/my-open-cases-means-open.md.

Measurements

Every claim above was measured against the booted app (memory driver, real seed data, real hooks), not read off the source.

The two predicates do NOT select the same set — this is a behaviour change, not a re-spelling. Over the seeded population of 38 cases:

predicate lowered filter rows
old {"is_closed":false} 30
new {"status":{"$nin":["resolved","closed"]}} 23

In A only (rows the rewrite removes): 7, every one of them status: resolved — CASE-00003, 00011, 00016, 00021, 00026, 00031, 00036. In B only: 0. Controls: a resolved row is reported inside A and outside B (the harness can tell them apart); an in_progress row is reported inside both (it can also report agreement).

Only closed carries the flag. Every crm_case.status option, written through the real hook and read back from storage:

new false · in_progress false · waiting_customer false · waiting_support false · escalated false · resolved false · closed true — 7 options, 1 with is_closed: true. There is no third closed-ish status, so the value list in the new predicate is complete.

The roster move is real work, not a no-op. With only the view edited, the two expected guards went red and nothing else did:

× is_closed keeps case_workflow.filter + my_open_cases.filter
    expected [ 'owner_id', 'status' ] to include 'is_closed'
× my_open_cases still narrows on is_closed and on no status
    expected [ 'owner_id', 'status' ] to include 'is_closed'

Bounded in-place fix, declared

test/case-create-form-narrowing.test.ts is not on the claim's declared file surface. Its LIFECYCLE_MAINTAINED roster asserted that is_closed keeps my_open_cases.filter, which is the same fact this PR changes seen from the create-form side — the red above is its retention assertion, quoted verbatim. The entry now reads keeps: ['case_workflow.filter'], with a comment naming the reason. case_workflow keeps the flag deliberately: that kanban is the lifecycle itself and resolved is a swimlane on it.

Testing

pnpm verify — all eight stages green on the pushed tree, 3a690f6d:

✓ Validation passed (1450ms)
tsc --noEmit                              (clean)
objectstack lint                          (clean)
✓ i18n lint gate: 0 `i18n/missing-*` issues
✓ source hygiene clean
✓ source token ratchet clean
    interaction layer ~37,429 tokens (ceiling ~40,000; headroom ~2,571)
✓ Build complete (1948ms)
Test Files  159 passed (159)
     Tests  3344 passed | 1 skipped (3345)
  • Unit tests pass
  • Linting passes
  • Build succeeds
  • New tests added — the roster move is the guard change

Checklist

  • I have added a changeset
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes

Additional Notes

Two things found while measuring, not touched here and reported separately for their own cards:

  1. content/docs/service/sla-and-escalation.mdx:144 and its two Chinese faces say crm_case ships seven views and enumerate them, omitting Unassigned — triage. cases.mdx:170 says eight, and eight is right. Stale in three faces.
  2. The paragraph under the views table reads "Six names this section used to list are not views at all" in English, but "of the seven names this section used to list, six are not views" in both Chinese faces. One face is describing a list the other does not have.

🤖 Generated with Claude Code

https://claude.ai/code/session_019hUuCQStzXGMFSX4dzww5t


Generated by Claude Code

The tab filtered on `is_closed`, which `case_sla_defaults` derives as
`status === 'closed'` and which therefore never flips on `resolved`. A tab
labelled "My Open Cases" listed an agent's finished work alongside their live
work. It now uses the same predicate the other live-work consumers already
state: `status not_in ['resolved', 'closed']`.

Measured on the seeded demo population (38 cases): the flag predicate returned
30 rows, 7 of them resolved; the status predicate returns 23. No row is added.

`my_open_cases` moves from the boundary roster of
test/live-work-predicate-parity.test.ts into its consumer roster, and joins the
is_closed offender sweep. `case_escalation_sharing` and `case_director_sharing`
stay exactly as declared: the `resolved` to `closed` review window is what
those standing grants exist for, and the reasoning is now written beside each
rule in the source and in its boundary-roster entry.

The `is_closed` retention entry in test/case-create-form-narrowing.test.ts
drops `my_open_cases.filter` and keeps `case_workflow.filter`, which is the
same fact from the create-form side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hUuCQStzXGMFSX4dzww5t
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 3, 2026 10:37am UTC

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants