Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
075cba4
feat(people): redesign TASKS column with per-requirement status rows
tofikwest Jul 1, 2026
d2ad593
feat(people): 2FA source backend — schema, read path, endpoints
tofikwest Jul 1, 2026
aded01f
refactor(integrations): universal CheckResultsService for reusing che…
tofikwest Jul 1, 2026
1d9c0ec
chore: merge release v3.95.2 back to main [skip ci]
github-actions[bot] Jul 2, 2026
7f75e36
feat(people): per-employee 2FA column from the org's selected 2FA source
tofikwest Jul 2, 2026
07808ba
feat(ui): responsive-by-default mandate + collapse 2FA selector on mo…
tofikwest Jul 2, 2026
f26957f
docs(skills): reconcile responsive-ui with design-system rules
tofikwest Jul 2, 2026
4f56111
Merge branch 'main' into tofik/people-tasks-column-redesign
tofikwest Jul 2, 2026
49bcc7e
test(people): assert loading skeleton presence, not just dash absence
tofikwest Jul 2, 2026
80100a4
Merge branch 'tofik/people-tasks-column-redesign' of github.com:tryco…
tofikwest Jul 2, 2026
28807c8
Merge branch 'tofik/people-tasks-column-redesign' into tofik/people-2…
tofikwest Jul 2, 2026
5d7f969
Merge pull request #3332 from trycompai/tofik/people-tasks-column-red…
tofikwest Jul 2, 2026
66edac2
fix(people): address cubic review on 2FA source
tofikwest Jul 2, 2026
532bbfe
Merge branch 'main' into tofik/people-2fa-source
tofikwest Jul 2, 2026
63a1282
style(people): tighten TASKS scorecard spacing and align value column
tofikwest Jul 2, 2026
dc44c73
Merge branch 'tofik/people-2fa-source' of github.com:trycompai/comp i…
tofikwest Jul 2, 2026
8747832
style(people): drop the redundant mini progress bar from TASKS rows
tofikwest Jul 2, 2026
e2176ad
fix(people): map missing-org cases to 404 on 2FA source endpoints
tofikwest Jul 2, 2026
21e2bef
fix(people): guard org existence before provider validation on POST t…
tofikwest Jul 2, 2026
fa94d79
Merge pull request #3333 from trycompai/tofik/people-2fa-source
tofikwest Jul 2, 2026
c9038a3
fix(people): label the toolbar source selects and stop date-chip wrap…
tofikwest Jul 2, 2026
43cd933
Merge branch 'main' into tofik/people-2fa-source
tofikwest Jul 2, 2026
2f18dae
fix(people): associate toolbar select labels for screen readers
tofikwest Jul 2, 2026
59cdce7
Merge branch 'tofik/people-2fa-source' of github.com:trycompai/comp i…
tofikwest Jul 2, 2026
ff6729e
Merge pull request #3336 from trycompai/tofik/people-2fa-source
tofikwest Jul 2, 2026
1925d81
style(people): brand-green counts + inline chip labels for source sel…
tofikwest Jul 2, 2026
234ba8e
style(people): self-explanatory sentence labels for the source selects
tofikwest Jul 2, 2026
341f540
Merge branch 'main' into tofik/people-2fa-source
tofikwest Jul 2, 2026
9f76eb4
Merge pull request #3337 from trycompai/tofik/people-2fa-source
tofikwest Jul 2, 2026
050bb87
style(people): uniform labels above every toolbar control
tofikwest Jul 2, 2026
9ff4d05
Merge branch 'main' into tofik/people-2fa-source
tofikwest Jul 2, 2026
a0ff49c
Merge pull request #3338 from trycompai/tofik/people-2fa-source
tofikwest Jul 2, 2026
03c7641
feat(people): split requirements into table columns + collapse filter…
tofikwest Jul 2, 2026
dc2be7b
feat(people): removable active-filter chips + always-visible table sc…
tofikwest Jul 2, 2026
5ac1099
fix(people): match filter-change handlers to the DS Select signature
tofikwest Jul 2, 2026
9e3cab1
fix(people): address cubic review on columns + filters
tofikwest Jul 2, 2026
e7c18b5
Merge branch 'main' into tofik/people-2fa-source
tofikwest Jul 2, 2026
459d7f3
Merge pull request #3339 from trycompai/tofik/people-2fa-source
tofikwest Jul 2, 2026
111cced
style(people): move source selects into a Sources popover
tofikwest Jul 3, 2026
f9de116
style(people): rename Sources button to Data sources
tofikwest Jul 3, 2026
87068c5
style(people): empty state with CTA in the Data sources popover
tofikwest Jul 3, 2026
3fdfeed
style(people): simpler, general empty-state copy in Data sources
tofikwest Jul 3, 2026
8d406c0
style(people): labeled empty slots in Data sources instead of prose
tofikwest Jul 3, 2026
389c6c6
style(people): query controls left, Data sources right
tofikwest Jul 3, 2026
1d1246a
style(people): rename Data sources button to Sync settings
tofikwest Jul 3, 2026
c84df2f
Merge pull request #3343 from trycompai/tofik/people-sources-popover
tofikwest Jul 3, 2026
aeb707e
fix(people): inline select dropdowns inside toolbar popovers
tofikwest Jul 3, 2026
b1598df
style(people): logos + tighter copy in the 2FA source dropdown
tofikwest Jul 3, 2026
8e568e9
style(people): friendlier labels in Sync settings
tofikwest Jul 3, 2026
7e4e8be
Merge pull request #3344 from trycompai/tofik/people-popover-nested-p…
tofikwest Jul 3, 2026
339f5f4
revert(people): portal={false} on in-popover selects broke positioning
tofikwest Jul 3, 2026
ec7cad7
Merge branch 'main' into tofik/people-popover-nested-portals
tofikwest Jul 3, 2026
d126cc6
Merge pull request #3345 from trycompai/tofik/people-popover-nested-p…
tofikwest Jul 3, 2026
4c4492d
fix(people): make the 2FA select uncontrolled like the sync select
tofikwest Jul 3, 2026
b3f5b2a
Merge branch 'main' into tofik/people-popover-nested-portals
tofikwest Jul 3, 2026
e7e276e
Merge pull request #3346 from trycompai/tofik/people-popover-nested-p…
tofikwest Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions .claude/skills/check-results-service/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
name: check-results-service
description: How to reuse ANY integration check's results in a feature via the universal CheckResultsService (apps/api integration-platform). Use whenever a feature needs data produced by an integration check — "show 2FA status on People", "surface AWS S3 findings in X", "reuse a check's results", "per-user/per-resource results from a connected integration", "which integrations feed task T". Read this BEFORE writing your own IntegrationCheckResult / CheckRunRepository query — don't hand-roll it.
---

# CheckResultsService — reuse any integration check's results

## The one idea

Integration checks (2FA, AWS S3 encryption, device posture, …) all write per-resource
results to one table (`IntegrationCheckResult`). **`CheckResultsService` is the single,
universal, read-only way to get those results into any feature.** It is deliberately
**feature-agnostic**: it fetches results and hands them back in a stable envelope, and it
does **not** know or care what the data means.

> **Service = fetch the results (generic). Feature = interpret + map + present.**

If you're about to query `IntegrationCheckResult` or `CheckRunRepository` directly from a
new feature — stop. Use this service instead.

- Service: `apps/api/src/integration-platform/services/check-results.service.ts`
- Exported from `IntegrationPlatformModule` — inject it into any feature module.
- Reference consumer (copy this): the 2FA feature — `two-factor-source.controller.ts`.

## When to use it

Use it whenever a feature needs the output of an integration check:
- "Show each employee's 2FA status on the People tab" (the current consumer)
- "Surface which S3 buckets failed encryption inside feature X"
- "Show device compliance per user from the MDM check"
- "List which connected integrations can supply data for task T"

## The API (3 methods)

```ts
// 1. Discover sources: which connected integrations can feed a task, + connection state.
listSourcesBoundToTask(organizationId, taskTemplateId): Promise<CheckSourceInfo[]>

// 2. The primitive: full results of a check's latest REAL run for ONE connection.
getLatestResultsByCheck({ organizationId, connectionId, checkId, resourceType? }): Promise<CheckResultRow[]>

// 3. Convenience: results for a task-bound check from a chosen source (provider slug).
// Resolves task -> check and slug -> connection for you.
getLatestResultsForTask({ organizationId, taskTemplateId, sourceSlug, resourceType? }): Promise<CheckResultRow[]>
```

Notes:
- "Latest REAL run" = newest run that isn't `inconclusive`/held and whose connection isn't
disconnected. Held runs (our-side self-heal) never leak to a feature.
- No row cap — you get the full result set (unlike the 30-row task-history display).
- `resourceType` filters rows (e.g. `'user'`, `'bucket'`). Omit to get all.
- Empty array = "no data" (source not bound/connected, or never really ran). Never throws
for "no results".

## The envelope you get back

```ts
interface CheckResultRow {
resourceId: string; // provider-native id: email (2FA), bucket ARN (S3), repo, …
resourceType: string; // 'user' | 'bucket' | …
passed: boolean; // did this resource pass the check
title: string;
description: string | null;
evidence: Prisma.JsonValue; // ← check-SPECIFIC payload. The service does NOT interpret it.
collectedAt: Date;
runId: string;
connectionId: string;
}
```

**The `evidence` rule (important):** the envelope is universal; the check-specific data lives
in `evidence` as raw JSON. The service never types or interprets it. **Your feature validates
`evidence` at its own edge with a zod schema** and reads only the fields it understands:

```ts
const TwoFaEvidence = z.object({ isEnrolledIn2Sv: z.boolean() });
const parsed = TwoFaEvidence.safeParse(row.evidence);
// use parsed.data.isEnrolledIn2Sv — never `as any`
```

For a simple pass/fail feature you often don't even need `evidence` — just use `row.passed`
and `row.resourceId` (that's all 2FA needs).

## How to add a NEW consumer (step by step)

Say a feature wants "which AWS S3 buckets failed encryption":

1. Inject the service: add `CheckResultsService` to your feature's constructor (the module
already exports it; import `IntegrationPlatformModule` if your module doesn't already).
2. Get results with the primitive (S3 usually spans many AWS connections, so you may loop
connections from `listSourcesBoundToTask` or your own connection lookup):
```ts
const rows = await this.checkResults.getLatestResultsByCheck({
organizationId,
connectionId,
checkId: 'aws-s3-encryption', // the check's manifest id
resourceType: 'bucket',
});
```
…or, if your feature lets the user PICK a source for a task (like 2FA does), use
`getLatestResultsForTask` with the task template id and the chosen `sourceSlug`.
3. Interpret in YOUR feature: map `resourceId`/`passed`, validate `evidence` with zod, shape
the response your UI needs. **Do not** add feature logic to the service.
4. Test with the service mocked (see `two-factor-source.controller.spec.ts`).

## Worked example — the 2FA feature (reference implementation)

The People-tab 2FA column is consumer #1. Study it end to end:

- `two-factor-source.controller.ts`
- `available-2fa-sources` → `listSourcesBoundToTask(org, TASK_TEMPLATES.twoFactorAuth)`
- `two-factor-statuses` → `getLatestResultsForTask({ org, taskTemplateId: twoFactorAuth,
sourceSlug: org.twoFactorSource, resourceType: 'user' })`, then maps `resourceId`→email
(lowercased) and `passed`→`enabled`/`missing`. Emails with no row are resolved to
"Not provided" on the client, never a false "missing".
- The controller owns the 2FA-specific bits (the `Organization.twoFactorSource` column, the
enabled/missing interpretation). The generic fetch is all in the service.

## Rules / do & don't

- ✅ DO put every "read a check's results" path through this service.
- ✅ DO validate `evidence` with zod in your feature. No `as any`.
- ✅ DO treat an empty array as "no data / not provided" — never a failure.
- ❌ DON'T query `IntegrationCheckResult` / `CheckRunRepository` directly from a feature.
- ❌ DON'T add feature-specific interpretation (enabled/missing, domain mapping) to the
service — it must stay universal and read-only.
- ❌ DON'T assume a source produces per-`user` (or email-keyed) rows — that's per-check. If a
chosen source's check emits a different `resourceType` or a non-mappable `resourceId`, your
feature should degrade gracefully (e.g. "Not provided"), not crash.

## Source selection is NOT part of this service

Which integration an org uses for a purpose (e.g. `Organization.twoFactorSource`) is
feature-owned config, mirroring `employeeSyncProvider` / `deviceSyncProvider`. Add a column
per feature. (If a 3rd/4th feature needs it, consider generalizing selection then — not
before.)
98 changes: 98 additions & 0 deletions .claude/skills/responsive-ui/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
name: responsive-ui
description: MANDATORY for any UI work in apps/app, apps/portal, or packages/design-system — every component, page, or layout change must work on mobile (~375px), tablet (~768px), desktop (~1280px), and large desktop (~1920px) BY DEFAULT, without being asked. Read this before writing or editing any JSX/TSX that renders visible UI. Triggers on: new component, page, layout, table, toolbar, form, modal/sheet, dashboard, "build UI", "add a column", "add a filter", any styling change.
---

# Responsive UI — default, not a feature request

## The rule

**Every UI change ships working at all four device classes. Nobody has to ask.**

| Device | Test width | Tailwind context |
|---|---|---|
| Mobile | 375px | base (no prefix) |
| Tablet | 768px | `md:` |
| Desktop | 1280px | `xl:` |
| Large desktop | 1920px | beyond `2xl:` |

Tailwind is **mobile-first**: unprefixed classes are the mobile layout; prefixes add
behavior at wider screens (`sm:` 640, `md:` 768, `lg:` 1024, `xl:` 1280, `2xl:` 1536).
Write the mobile layout first, then widen — not the other way around.

A change is NOT done until you have reasoned through (or better, viewed via the
`webapp-testing` skill / browser dev tools) what it looks like at 375, 768, 1280,
and 1920. If you only checked one width, you checked none.

## Works WITH the design system — precedence rules

This skill layers on top of the `ui` skill; **where they touch, the design-system
rules win**. Responsiveness is achieved THROUGH the DS, never around it:

- **Never put responsive classes (or any `className`) on DS components**
(`Text`, `Stack`, `HStack`, `Badge`, `Button`, …) — they don't accept it. Put
breakpoint utilities on a wrapper `<div>` (the `ui` skill's "Layout with Wrapper
Divs" pattern):
```tsx
// ✅ breakpoints on the wrapper, DS component untouched
<div className="hidden sm:block"><Badge variant="outline">Active</Badge></div>
// ❌ className on a DS component
<Badge className="hidden sm:block">Active</Badge>
```
- **Reach for DS layout primitives first** (`PageLayout`, `Stack`, `HStack`,
`Section`) — they carry responsive spacing already. Raw responsive divs are for
what the primitives don't cover, not a replacement for them.
- **Arbitrary pixel values stay an anti-pattern** (`w-[847px]` ❌ — per the `ui`
skill). Prefer the standard Tailwind scale (`max-w-sm`, `w-48`, `max-w-xs`). A
fixed control width like `w-[200px]` is acceptable ONLY when it matches an
established pattern already used on that surface, and it must still carry a
responsive strategy (see below).

## Non-negotiables

1. **The page body never scrolls horizontally.** Wide content (tables, code, diagrams)
scrolls inside its own container, never the page.
2. **No fixed width without a responsive strategy.** A fixed-width control is only
acceptable if the element hides, shrinks, or wraps on smaller screens
(`hidden sm:block`, `w-full md:max-w-xs`, or a wrapping parent) — with the
breakpoint classes on a wrapper div, never on a DS component.
3. **Touch targets** on mobile: interactive elements ≥40px tall; don't rely on hover
for anything essential (no hover on touch devices).
4. **Test with real content lengths** — long names, emails, 33-item counts. Use
`truncate` / `min-w-0` on flex children that hold text.
5. **Large desktops:** content must not stretch into unreadable full-width lines —
cap with `max-w-*` containers where the layout doesn't already.

## Repo patterns (use these, don't invent)

- **Tables**: the design-system `Table` already wraps rows in `overflow-x-auto` —
wide tables scroll horizontally inside themselves on mobile. That is the accepted
pattern for dense admin tables. Do NOT try to reflow table columns per breakpoint;
do keep cells reasonable (`min-w-* max-w-*` on cell content is fine).
- **Toolbars / filter rows**: primary control (search) is `w-full md:max-w-[300px]`;
secondary controls (filters, source selectors) collapse on phones with
`hidden sm:block`. Example: the People tab toolbar in
`apps/app/.../people/all/components/TeamMembersClient.tsx`.
- **Page shells**: use design-system `PageLayout` / `Stack` / `HStack` — they carry
the responsive spacing; don't rebuild them with raw divs.
- **Grids**: `grid-cols-1 md:grid-cols-2 xl:grid-cols-3` style progressions — never a
fixed column count for all widths.
- **Sheets/Modals**: design-system `Sheet`/`Dialog` are responsive already; don't fix
their widths with arbitrary values.
- **Images/logos**: constrain (`max-w-full`, explicit small sizes); never let an
image force layout width.

## Checklist before "done" (run mentally or in the browser)

- [ ] 375px — nothing overflows the viewport; controls usable or intentionally hidden; text truncates instead of breaking layout
- [ ] 768px — secondary controls visible; layout uses available width sensibly
- [ ] 1280px — the intended "design" width; matches mockups
- [ ] 1920px — no absurdly stretched content; whitespace balanced
- [ ] Long content (names, emails, high counts) doesn't break any of the above
- [ ] Tests: if a component hides/reflows per breakpoint in a way that matters, its test asserts the class contract (e.g. `hidden sm:block`)

## When you can skip a width

Admin-only dense screens (e.g. internal tooling) may treat mobile as "usable via
horizontal scroll" rather than fully reflowed — but that must be a conscious choice
following the existing pattern of that page, never an accident of not checking.
11 changes: 11 additions & 0 deletions .claude/skills/ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,14 @@ import { Plus, X } from 'lucide-react';
<div className="bg-[#059669]"> // Hardcoded colors
<div className="w-[847px]"> // Arbitrary values
```

## Responsive (MANDATORY — read the `responsive-ui` skill)

Every UI change must work on mobile (375px), tablet (768px), desktop (1280px), and
large desktop (1920px) **by default — nobody has to ask**. Tailwind is mobile-first:
write the base (mobile) layout, widen with `sm:`/`md:`/`lg:`/`xl:`. No fixed widths
without a responsive strategy (`hidden sm:block`, `w-full md:max-w-xs`, or a wrapping
parent). All the rules above still apply: breakpoint classes go on wrapper divs, never
on DS components, and arbitrary pixel values remain an anti-pattern. Wide tables scroll
inside the DS `Table` (`overflow-x-auto`), never the page. Full rules + repo patterns +
checklist: `.claude/skills/responsive-ui/SKILL.md`.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Every customer-facing API endpoint MUST have:
- **DS components that do NOT accept `className`**: `Text`, `Stack`, `HStack`, `Badge`, `Button` — wrap in `<div>` for custom styling
- **Layout**: Use `PageLayout`, `PageHeader`, `Stack`, `HStack`, `Section`, `SettingGroup`
- **Patterns**: Sheet (`Sheet > SheetContent > SheetHeader + SheetBody`), Drawer, Collapsible
- **Responsive (MANDATORY)**: every UI change must work on mobile (375px), tablet (768px), desktop (1280px), and large desktop (1920px) by default — no one has to ask. See the `responsive-ui` skill for breakpoints, repo patterns, and the checklist.
- **After editing any frontend component**: Run the `audit-design-system` skill to catch `@trycompai/ui` or `lucide-react` imports that should be migrated

## Data Fetching
Expand Down
4 changes: 4 additions & 0 deletions apps/api/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ const module = await Test.createTestingModule({
- Use Prisma via `@trycompai/db`
- Always scope queries by `organizationId` for multi-tenancy
- Use transactions for operations that modify multiple records
- **Reusing an integration check's results in a feature?** Use `CheckResultsService`
(`integration-platform/services/check-results.service.ts`) — the universal, read-only way
to get any check's per-resource results. Do NOT query `IntegrationCheckResult` /
`CheckRunRepository` directly from a feature. See the `check-results-service` skill.

### Gotchas

Expand Down
Loading
Loading