Skip to content

refactor(frontend): seal readonly leaks + exactOptional casts (Group 70: #1593, #1594, #1590)#1597

Merged
adamflagg merged 5 commits into
mainfrom
feature/group-70-fe-typesafety
May 21, 2026
Merged

refactor(frontend): seal readonly leaks + exactOptional casts (Group 70: #1593, #1594, #1590)#1597
adamflagg merged 5 commits into
mainfrom
feature/group-70-fe-typesafety

Conversation

@adamflagg
Copy link
Copy Markdown
Owner

@adamflagg adamflagg commented May 21, 2026

Closes #1593, closes #1594, closes #1590.

Group 70 — type-safety follow-ups deferred out of the §3 frontend-modernization PRs (#1585#1592). Pure type-only hardening; no runtime behavior change. Each fix makes a type honest so a load-bearing cast can be deleted or flipped to satisfies.

Changes

  • refactor(frontend): seal readonly leak in createGraphElements cast (SocialNetworkGraph) #1593 — widen createGraphElements's four array params to readonly + narrow graph.ts CrossScopeEdge.edge_type to 'request' (matches the generated type; the API only emits 'request' cross-scope edges). All four as Parameters<…>[N] casts at SocialNetworkGraph.tsx are deleted.
  • refactor(frontend): seal readonly leak in BunkSocialGraphModal cast #1594 — seal the local BunkGraphData.nodes/.edges readonly. The as unknown as BunkGraphData stays (the cached GraphData structurally diverges — bunk_cm_id/bunk_name/metrics/health_score), but downstream read-only protection is restored.
  • refactor(frontend): two as casts mask exactOptionalPropertyTypes mismatches (blocks satisfies) #1590 — make two literals honest under exactOptionalPropertyTypes:
    • useCamperEnrollment: flipping as Campersatisfies Camper surfaced two masked violations — assigned_bunk_cm_id (number | undefined into optional ?: number, now conditional-spread) and expand.session/.assigned_bunk (explicit undefined into ?: … | null, now ?? null). Also tightened a now-redundant expand?. optional chain.
    • useSiblings: as SiblingWithEnrollmentsatisfies; conditional-spread the optional session key; typed session_type as the CampSessionsSessionTypeOptions enum; dropped the manual s is SiblingWithEnrollment predicate in favor of TS 6's inferred narrowing (the precise literal type is a subtype, satisfying the hook's return boundary).

Test Plan

  • npm run type-check passes (both tsconfig.json + tsconfig.node.json) — each cast was verified load-bearing (removed → tsc fails) before its supporting type change.
  • npm run lint — 0 errors, no new warnings (the two introduced by the type-honesty change are fixed).
  • npx vitest run — 4450 pass, 0 fail.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Improved internal type safety by strengthening immutability constraints on data structures used for graph rendering and camper enrollment information.
    • Enhanced type accuracy for cross-scope edge data and session information to better align with backend contracts.
    • Simplified null-checking logic in enrollment and sibling data processing.

Review Change Stack

adamflagg and others added 5 commits May 21, 2026 15:10
…1593)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…a satisfies (#1590)

Flip 'as Camper' to 'satisfies Camper', which surfaced two masked
exactOptionalPropertyTypes violations: assigned_bunk_cm_id (number|undefined
into optional ?:number) now conditional-spread, and expand.session/
.assigned_bunk (undefined into optional ?:...|null) now coerced with ?? null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nest via satisfies (#1590)

Flip 'as SiblingWithEnrollment' to 'satisfies'. Type session_type as the
CampSessionsSessionTypeOptions enum (matches the literal), conditional-spread
the optional session key instead of assigning explicit undefined, and drop the
manual 's is SiblingWithEnrollment' predicate in favor of TS6's inferred
narrowing (the precise literal type is a subtype, satisfying the return type).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The satisfies Camper change makes the literal's expand known-present, so the
optional chain on a.expand/b.expand in the sort comparator is provably
redundant (eslint no-unnecessary-condition). Tighten to a.expand.session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4e1c9489-7681-41f9-b8fc-ba8fe7c8d528

📥 Commits

Reviewing files that changed from the base of the PR and between 01db8f2 and 42b4814.

📒 Files selected for processing (7)
  • frontend/src/components/BunkSocialGraphModal.tsx
  • frontend/src/components/SocialNetworkGraph.tsx
  • frontend/src/components/graph/cytoscapeStyles.ts
  • frontend/src/hooks/camper/types.ts
  • frontend/src/hooks/camper/useCamperEnrollment.ts
  • frontend/src/hooks/camper/useSiblings.ts
  • frontend/src/types/graph.ts

📝 Walkthrough

Walkthrough

Sealed readonly array leaks in the graph rendering pipeline and replaced unsafe type casts with satisfies checks in camper enrollment hooks to enforce immutability and exactOptionalPropertyTypes compliance.

Changes

Graph immutability enforcement

Layer / File(s) Summary
Cross-scope edge type literal and function signature contract
frontend/src/types/graph.ts, frontend/src/components/graph/cytoscapeStyles.ts
CrossScopeEdge.edge_type narrowed to literal 'request'. createGraphElements signature updated to accept readonly array inputs (nodeData, edgeData, crossScopeEdges, crossScopeNodes), fixing the type contract.
Graph data interface and caller updates
frontend/src/components/BunkSocialGraphModal.tsx, frontend/src/components/SocialNetworkGraph.tsx
BunkGraphData.nodes and BunkGraphData.edges marked readonly. Unsafe Parameters<typeof createGraphElements>[N] casts removed in SocialNetworkGraph; arguments now passed directly with inferred types.

Camper and sibling type safety

Layer / File(s) Summary
SiblingWithEnrollment type and session_type enum
frontend/src/hooks/camper/types.ts
Imported CampSessionsSessionTypeOptions and updated SiblingWithEnrollment.session.session_type from string to CampSessionsSessionTypeOptions.
useCamperEnrollment type-safe construction
frontend/src/hooks/camper/useCamperEnrollment.ts
assigned_bunk_cm_id now conditionally included only when assignedBunk.cm_id is defined. expand.session and expand.assigned_bunk set to explicit null defaults. Replaced as Camper cast with satisfies Camper check. Sort comparator updated to use optional chaining on expand?.session.
useSiblings type-safe object construction and nullability
frontend/src/hooks/camper/useSiblings.ts
session field constructed via conditional spread (omitted when undefined) instead of ternary assignment. Replaced as SiblingWithEnrollment cast with satisfies check. Null-removal filter simplified to rely on s !== null narrowing.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Assessment against linked issues

Objective Addressed Explanation
Seal readonly leak in createGraphElements cast by updating function signature and removing casts (#1593)
Seal readonly leak in BunkSocialGraphModal by making BunkGraphData properties readonly (#1594)
Replace unsafe as casts with satisfies checks and fix exactOptionalPropertyTypes violations in useCamperEnrollment and useSiblings (#1590)
Narrow CrossScopeEdge.edge_type to literal 'request' (#1590, #1592)

Possibly related PRs

  • adamflagg/kindred#1592: Preceding readonly-enforcement PR that made GraphData, GraphNode, and GraphEdge readonly; this PR continues the immutability hardening downstream through function signatures and interfaces.
  • adamflagg/kindred#1591: Related satisfies-based refactoring in camper/sibling hooks applying the same "replace as with satisfies" pattern for type safety.
  • adamflagg/kindred#1560: Concurrent changes to createGraphElements in cytoscapeStyles.ts handling cross-scope edge filtering.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/group-70-fe-typesafety

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

@adamflagg adamflagg merged commit 651453b into main May 21, 2026
23 checks passed
@adamflagg adamflagg deleted the feature/group-70-fe-typesafety branch May 21, 2026 22:34
adamflagg added a commit that referenced this pull request May 21, 2026
#1598)

Closes #1596.

Finishes the app-types `readonly` contract opened by #1595 — same latent
readonly-leak class as the graph-type follow-ups #1593/#1594 (shipped in
#1597). Two consumers were intentionally left out of #1595 to keep that
a pure type-only change.

## Changes

1. **`AllCampersView.tsx`** — drop the now-redundant `as Camper[]` cast.
#1595 widened `buildCamperRows` to accept `readonly Camper[]`
(`csvExportHelpers.ts:73`), and `filteredCampers` is `MergedCamper[]`
(`MergedCamper extends Camper`), so it's directly assignable. The
standing cast only suppressed future compiler signal if the contract
changed.

2. **`mergeMultiSessionCampers.ts`** — mark `AdditionalSession`'s fields
and `MergedCamper.additionalSessions` `readonly`, matching the
now-`readonly` `Camper` that `MergedCamper` extends. The builder
constructs each `AdditionalSession` via `.map()` into fresh objects, so
the element type going `readonly` is a clean change.

Pure type-only hardening — **no runtime behavior change**, no active
mutation existed.

## Tests

Adds the first tests for `mergeMultiSessionCampers` (previously
untested):
- **Behavioral coverage** — multi-enrollment merge produces one entry
with `additionalSessions`; single-session camper untouched.
- **Readonly contract** — `@ts-expect-error` assertions enforced by `tsc
--noEmit`. Verified red→green: before the change the directives are
unused (TS2578); after, they type-check.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Tests**
* Added comprehensive test suite for multi-session camper merging
functionality, including validation of camper data consolidation and
immutability constraints.

* **Refactor**
* Enhanced type safety in CSV export and camper data handling to prevent
potential bugs and ensure data integrity during export operations.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/adamflagg/kindred/pull/1598?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant