← all branches

fix/sites-polish

needs attentionviewing older commit
bf60412 · fullPR #290reviewed 2026-07-09 04:33 UTC3H · 7M · 8L · 4I
The branch
Purpose
Polish follow-up to sites 1:N (#279): fix two regressions surfaced on preview.
Goal
Replace full-width Agrupar switch with compact toolbar control; ensure site-split copies assignees so teammates keep visibility of moved RPUs.
Sub-goals
  • SG-1: compact GroupControl popover in toolbar replacing Switch row
  • SG-2: assignee-copy on site split via bulk INSERT-SELECT in same transaction
The changes (whole branch)
What
New GroupControl component; groupBySite state lifted to page; createSiteShell extended with optional copyAssigneesFromSiteId; handler org-gates source site; new copyAllToSite bulk query in site-assignee.
Why
Agrupar Switch consumed a full-width row and looked inconsistent. Split path left new sites unassigned causing visibility gaps for teammates.
Areas
apps/platform+9925domains/core+431packages/api+90
Blast
10 files, +151/-26 across 3 areas. No schema migration. Additive optional API field.
auth transaction-boundary
typecheck· pnpm turbo typecheck passeseslint· 0 errors on touched filesContractsTable memo test· 2/2 passCI· no CI check data availableCodeRabbit· no .coderabbit.yaml in repo

Findings · 24

correctness5

medium

TOCTOU: findByPublicId pre-flight outside transaction — soft-deleted source site passes org-gate

apps/platform/src/api/handlers/sites.handler.ts:71

Handler reads source site on bare database before opening the shell transaction. findByPublicId has no isNull(deletedAt) guard, so a just-soft-deleted site passes org-gate; copyAllToSite then copies 0 assignees silently.

medium

copyAllToSite result extraction uses a driver-shape cast that may return wrong count

domains/core/src/site-assignee/site-assignee.queries.ts:244

Dual-branch cast hedges between postgres.js and node-pg. Rows are still inserted correctly; only the returned count may be wrong.

low

collapsedSites not reset when groupBySite toggled off then on

apps/platform/src/app/[locale]/(dashboard)/bills/contratos/_components/ContractsTable.tsx:165

low

Inline CreateSiteCommand intersection in handler duplicates shell type

apps/platform/src/api/handlers/sites.handler.ts:84

info

copyAllToSite failure aborts site creation with generic 500

domains/core/src/site/site.shells.ts:84

security3

low

Soft-deleted source site passes org-gate — unintended assignee copy possible

apps/platform/src/api/handlers/sites.handler.ts:71

info

Assignee copy permitted at member role

apps/platform/src/api/handlers/sites.handler.ts:59

info

SQL in copyAllToSite is fully parameterized — no injection risk

domains/core/src/site-assignee/site-assignee.queries.ts:236

conventions4

high

Handler calls siteQueries.findByPublicId directly — canonical form says handlers use shells only

apps/platform/src/api/handlers/sites.handler.ts:64

medium

createSiteShell accepts anonymous inline intersection instead of named command type

domains/core/src/site/site.shells.ts:51

medium

JSDoc on copyAllToSite partially documents the obvious

domains/core/src/site-assignee/site-assignee.queries.ts:226

low

GroupControl hardcodes Spanish strings instead of next-intl

apps/platform/src/app/[locale]/(dashboard)/bills/contratos/_components/GroupControl.tsx:40

tests5

high

copyAllToSite has zero integration test coverage

domains/core/src/site-assignee/site-assignee.queries.ts:236

high

Handler org-gate for copyAssigneesFromSitePublicId is untested

apps/platform/src/api/handlers/sites.handler.ts:70

medium

createSiteShell assignee-copy path untested at shell layer

domains/core/src/site/site.shells.ts:84

medium

ContractsTable memo test does not exercise groupBySite=true

apps/platform/src/app/[locale]/(dashboard)/bills/contratos/_components/ContractsTable.tsx:144

low

No assertion that copyAssigneesFromSiteId is excluded from decideCreateSite input

domains/core/src/site/site.shells.ts:65

improvement7

medium

Inline type intersection should be a named type shared by shell and handler

apps/platform/src/api/handlers/sites.handler.ts:84

medium

Conditional spread should be a direct field — type already accepts undefined

apps/platform/src/api/handlers/sites.handler.ts:90

low

copyAllToSite return count is dead weight — consider void

domains/core/src/site-assignee/site-assignee.queries.ts:231

low

GroupControl file-level JSDoc restates the obvious

apps/platform/src/app/[locale]/(dashboard)/bills/contratos/_components/GroupControl.tsx:3

low

GroupControl open state is partially redundant with Radix native close

apps/platform/src/app/[locale]/(dashboard)/bills/contratos/_components/GroupControl.tsx:23

low

Hardcoded 1 badge in GroupControl trigger is premature UX

apps/platform/src/app/[locale]/(dashboard)/bills/contratos/_components/GroupControl.tsx:38

low

Double cast in copyAllToSite should use typed execute<T> pattern

domains/core/src/site-assignee/site-assignee.queries.ts:244

History · 3 commits

  1. ce41f08safeincremental0H · 0M · 0L2026-07-09 05:27
  2. c5ea5a5needs attentionincremental1H · 2M · 3L2026-07-09 04:53
  3. bf60412needs attentionfull3H · 7M · 8L2026-07-09 04:33current