Implement the Button component from the Probo Radix UI Figma: a
<button> with size 1-4, the classic/solid/soft/surface/outline/ghost
variants across the sand/gold/red/green/amber/sky scales with
highContrast, iconStart/iconEnd slots, and a loading state that
shows only a spinner. Corner radius is bound to size per the design
(radius is theme-global, not a per-instance prop). Includes a paired
ButtonSkeleton. Styling uses tailwind-variants/lite.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Avatar component from the Probo Radix UI Figma on top
of Base UI's avatar primitive (image load + fallback). It supports
size 1-9, solid/soft variants across the sand/gold/red/green/amber/
sky scales with highContrast, a radius scale, and a fallback node
for initials or an icon, plus a paired AvatarSkeleton. Styling uses
tailwind-variants/lite slots (root/image/fallback).
Add @base-ui/react to @probo/ui — the first v2 component built on a
Base UI primitive.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Heading primitive from the Probo Radix UI Figma: it
renders the semantic h1–h6 chosen by a numeric level prop (driving
the document outline) while size controls the visual scale
independently, defaulting to size 6 / bold weight per the design.
Add a paired HeadingSkeleton and a story.
Factor the shared size/weight/align/color variants and the color ×
highContrast compound variants out of the Text tv so Text and
Heading differ only in their defaults.
Signed-off-by: Émile Ré <emile@probo.com>
The numbered v2 token scales (text-1…9, rounded-1…6, shadow-1…6)
share a prefix with the color/utility namespaces, and tailwind-merge
(which tailwind-variants runs by default) cannot tell a numeric
text-3 from a color step, so it silently dropped the size whenever a
text-<color> was present. Switch the v2 kit to the merge-free
tailwind-variants/lite entrypoint (added in v3): upgrade
tailwind-variants to ^3.2.2, import tv/VariantProps from
tailwind-variants/lite in the typography primitives, and drop the
per-call twMerge:false.
The legacy v1 kit stays on tailwind-variants (merge intact). Document
the rationale in ui.md and add a packages/ui/src/v2 ESLint guard that
forbids tailwind-variants, tailwind-merge, and clsx.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the first v2 typography component from the Probo Radix UI
Figma: a Text primitive rendering a single span with size, weight,
align, color, and highContrast variants (color x highContrast
resolves to the sand/gold/red/green/amber/sky text step 11 or 12),
plus a paired TextSkeleton. Styling is tailwind-variants only, with
the scale shared through a variants module per the kit guides.
Stand up a theme-isolated v2 Storybook (.storybook-v2) loading the
v2 theme with a light/dark toggle, since v1 and v2 cannot share one
Tailwind build; the v1 build now excludes src/v2.
Signed-off-by: Émile Ré <emile@probo.com>
The @probo/eslint-plugin-relay-types shim only types an
`eslint-plugin-relay` import, which no app makes anymore now that
linting runs through the single root eslint.config.mjs. The apps'
tsconfig.node.json even pulled it in for a per-app eslint.config.mjs
that no longer exists.
Remove the devDependency and the `types` entry from each app's
node tsconfig, and drop the stale eslint.config.mjs include. The
shim stays for packages/eslint-config, its real consumer.
Signed-off-by: Émile Ré <emile@probo.com>
Apply small follow-up edits to the frontend documentation: the
AGENTS index, the forms, react-components, and ui guides.
Signed-off-by: Émile Ré <emile@probo.com>
Extract the awaitable useMutation into @probo/relay as a
createUseMutation factory that delegates feedback to an injected
MutationNotifier, keeping the package free of UI and i18n
dependencies. compliance-portal binds it to its Base UI toast +
i18next + formatError stack and imports it by explicit path
(#/lib/relay/useMutation), dropping the lone intra-app barrel; a
compliance-portal-scoped no-restricted-imports rule forbids
react-relay's useMutation.
Bring packages/relay and packages/routes into the shared ESLint
scope and fix the violations that surfaced, and deprecate the
legacy withQueryRef / loaderFromQueryLoader helpers. Document the
shared-hook pattern and the "index.ts for package entrypoints only"
rule in the relay, hooks, and app-arborescence guides.
Signed-off-by: Émile Ré <emile@probo.com>
Relay 21 made the filename-prefix requirement opt-in for non-Haste
projects, and the relay/graphql-naming lint rule only covers
operations and legacy fragment containers, leaving hooks-based
fragment names unguarded. Re-enable compiler enforcement via
enforce_module_name_prefix_for_non_haste so fragments keep the
collision-free, navigable <ModuleName>_<localName> convention, and
document the rationale in the Relay guide. The compiler accepts the
current sources unchanged, so existing names already comply.
Signed-off-by: Émile Ré <emile@probo.com>
The v2 token guide endorsed raw <h1>/<p> with hand-applied step
classes, which invites token drift across app code. Add a UI guide
section establishing that content text flows through kit typography
components (Text, Heading, Code) and that raw elements are reserved
for kit internals and structural, non-textual nodes. Cross-link it
from the typography token section so the two guides stay aligned.
Signed-off-by: Émile Ré <emile@probo.com>
Add the frontend guides the v2 UI kit and compliance-portal need but
that the first rework left uncovered: forms, routing, client state, and
permission-gated UI.
forms.md documents a tiered approach on Base UI Field/Form -- native
constraints, then a validate function, then zod parsed in onSubmit, and
react-hook-form only for large or dynamic forms -- and drops the custom
useFormWithSchema wrapper. routing.md covers @probo/routes, navigation,
typed params, URL-as-state, redirects, auth/protected routes, and the
folded-in no-outlet-context rule. state-management.md gives a decision
order across Relay, URL, local state, context, and zustand.
permissions.md gates UI on the canUpdate/canDelete permission(action:)
fields without re-encoding authorization in the client.
Rename v2-colors.md to v2-tokens.md and add the typography, radius,
shadow, and native-spacing scales alongside color. Extend ui.md with
user feedback, empty-state, and accessibility sections; standardize
toasts on Base UI's Toast (Toast.useToastManager) and retire the legacy
useToast across ui.md, forms.md, error-handling.md, and relay.md. Add an
Intl formatting section to i18n.md and a non-Relay HTTP / file
upload-download section to ts-style.md. Update the AGENTS.md index and
the v2-color-scale cursor rule for the new and renamed guides.
Signed-off-by: Émile Ré <emile@probo.com>
Make contrib/claude the single source of truth for v2 frontend work
on the compliance-portal app and packages/ui/src/v2, treating console
and the legacy @probo/ui tree as non-compliant code to migrate rather
than precedent.
Rewrite ui.md around the v2 kit: flat folders, Base UI as the headless
layer styled thinly (controlled open/onOpenChange, no imperative ref or
cloneElement plumbing), tailwind-variants only, separate components over
structure-changing variants, and bundle-safe skeletons that never drag
Base UI into the loading path.
Add a naming/suffix taxonomy to react-components.md, replacing the
Table/Row and connection-item Card suffixes with List/ListItem, and add
an error/fallback props convention. Document _lib and _locales special
folders plus routes.ts placement in app-arborescence.md, with at most
one _locales per routes.ts.
Add error-handling.md (reusable ErrorBoundary usable at any level plus
async try/catch) and i18n.md (i18next key-based catalogs). Update the
relay file-organization and fragment examples, the connection-item
cursor rule, and the AGENTS.md index to match.
Signed-off-by: Émile Ré <emile@probo.com>
Wire up react-router with a root layout route and an index home page,
following the single-arborescence conventions: pages live under pages/,
routes are built from AppRoute via routeFromAppRoute, and route bundles
load lazily behind a Suspense fallback.
Add the @probo/routes and @probo/react-lazy workspace dependencies that
the route setup relies on.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce a lib/http module with buildEndpoint and getPathPrefix,
extracted from the trust app so the compliance portal can resolve its
GraphQL endpoint with the same path-prefix handling.
Signed-off-by: Émile Ré <emile@probo.com>
The compliance-portal scaffold had an index.html pointing at a
src/main.tsx that did not exist, and carried stale config copied
from another app, so the bundle could not build.
Add the React entrypoint (main.tsx, App.tsx), a Tailwind index.css
wired to the standalone v2 theme, and vite-env.d.ts for the CSS
side-effect import and env typing. Scope the Tailwind @source scan
to packages/ui/src/v2 since this is a v2-only build, and use v2
color and type scales in index.html and App.
Drop the deprecated baseUrl from tsconfig.app.json and remove the
invalid react() babel relay option, which is unsupported by
@vitejs/plugin-react 6 and unneeded until Relay queries exist.
Signed-off-by: Émile Ré <emile@probo.com>
The compliance-portal app still carried the per-workspace ESLint
scaffold that the rest of the monorepo dropped when linting was
centralized into the root eslint.config.mjs. It defined its own
local config, lint script, and lint-only devDeps, and was not
covered by the root lint script, so it was never linted.
Remove the local eslint.config.mjs, the lint script, and the
eslint/@probo/eslint-config devDeps, then add the app to the root
appDirs so the shared ts, react, and relay rule sets apply to it.
Replace the explicit per-directory list in the root lint script
with a bare `eslint .`, which the root config was already designed
for via its globalIgnores. Ignore pkg/** so the vendored minified
JS under the Go tree is not linted.
Signed-off-by: Émile Ré <emile@probo.com>
Add the root configuration files for apps/compliance-portal, the
refactor of the legacy trust center SPA. The scaffold mirrors the
existing trust app (Vite, Tailwind, Relay, TypeScript project
references, ESLint) and targets the trust v1 GraphQL schema, which
the compliancepage backend already serves under /api/trust/v1.
Wire the app into the root relay.config.json with a dedicated
complianceportal project so the Relay compiler emits artifacts for
its sources. External dependencies are pinned to current latest
versions rather than matching the older majors used by the other
apps.
Signed-off-by: Émile Ré <emile@probo.com>
Convert the hand-rolled webhook Sender loop into a kit worker.Handler so
the sender gets the standard worker recovery, metrics and tracing
instead. Claim loads the next unprocessed webhook data and Process
performs the deliveries; behaviour is preserved with a single-concurrency
worker on the existing poll interval.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Allow install scripts for the packages we actually need (esbuild for
Vite builds, fsevents for dev file watching, unrs-resolver for n8n
linting) and deny the rest (core-js, isolated-vm,
eslint-plugin-n8n-nodes-base) so npm 11's install-script warning is
covered.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Add Pylon, OpenRouter, incident.io and Brevo as access-review connectors.
All are API-key, single-tenant providers (Pattern 3): the key identifies
one tenant, so there is no OAuth flow, picker UI, or bootstrap/helm
configuration.
- Pylon: Bearer token, GET /users; resolves each user's opaque role_id to
a role name via GET /user-roles, with cursor pagination.
- OpenRouter: Bearer management key, GET /api/v1/organization/members. The
endpoint requires an organization account -- a personal key authenticates
but returns 404 -- so the connection probe rejects 404 on top of 401/403
(doProbeRequest gained an opt-in extra-reject set) to surface a non-org
key at connect time instead of mid-campaign.
- incident.io: Bearer token, GET /v2/users. Its OAuth is outbound-only, so
the API key is the inbound path; live base_role/custom_roles take
precedence over the deprecated role enum.
- Brevo: API key in the api-key header (Registration.APIKeyHeader), GET
/v3/organization/invited/users. A live recording corrected the documented
schema: is_owner is a JSON boolean (not a string) and an id field is
present, so it is used as the stable ExternalID.
The OpenRouter and Brevo cassettes are anonymized live recordings; Pylon
and incident.io use hand-authored fixtures (no self-serve test tenant). The
shared three-valued active-status mapping is consolidated into
activeFromStatus in driver.go.
Each adds the enum value, migration, GraphQL binding, provider
Registration, a driver with a cassette-driven test, and a brand logo.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Introduce a Resolver that owns env lookup and typed parsing for
probod-bootstrap. Env values prefixed with aws://<secret-id> are
fetched from AWS Secrets Manager (plaintext SecretString); each
secret ID is cached per run. Builder now takes a Resolver only.
Prefix every probod-bootstrap input with PROBOD_ so bootstrap config
does not collide with unrelated process environment (for example
AWS_* used by other tooling). Secrets Manager authentication uses
the standard AWS SDK default chain (AWS_REGION, IAM role, profile);
PROBOD_AWS_* vars configure S3 in the generated config only.
Update Helm deployment env names, GNUmakefile dev-config, Lima
provision, e2e testutil, compose.prod.yaml, and docs.
Deployments must rename bootstrap env vars to PROBOD_* (e.g.
AUTH_COOKIE_SECRET → PROBOD_AUTH_COOKIE_SECRET).
BREAKING CHANGE: all env vars are now prefixed by `PROBOD_`.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
When a new minor version is published, still-REQUESTED signature requests
on the previous version now move onto the newly published version, keeping
the same signature row so the notification schedule (count and last-notified
time) is preserved. SIGNED signatures are left untouched.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
When an email link points to an employee signature or approval document
that has been deleted or is no longer accessible to the user, the detail
pages rendered an infinite spinner. Redirect to the signatures/approvals
tab list instead, both when the document resolves to null and when it has
no accessible versions.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
DocuSign approved our partner integration, so the connector can now
complete a real OAuth2 authorization-code flow. The integration key
has PKCE enabled, so RequiresPKCE is set; the confidential grant still
authenticates the token exchange with Basic auth and replays the
verifier as the documented hardening layer.
A DocuSign user may have access to several accounts, so this replaces
the previous auto-default-account behavior with a Pattern-1 picker:
the user chooses the account after OAuth, the choice is stored on
DocuSignConnectorSettings, and the driver and name resolver resolve
the selected account's data-center base URI from /oauth/userinfo.
Other changes:
- Request the extended scope so the refresh token's 30-day window
rolls on each use; without it the token hard-expires 30 days after
consent and breaks the connection.
- Drop API-key support: DocuSign has no static API key, only OAuth.
- Return ("", nil) from the name resolver on terminal failures so the
source-name worker does not retry a revoked token forever.
- Add a driver test and cassette; the test previously skipped in CI
for lack of a cassette.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Heroku's connection-status probe used a static ProbeURL, which the
generic probe issues with `Accept: application/json`. Heroku negotiates
the API version through the Accept media type and returns 400 for an
unversioned request, which doProbeRequest reads as "connected" -- so the
probe never caught a revoked token (it only surfaced at the first
ListAccounts).
Probe via a probeHeroku closure that sends
`Accept: application/vnd.heroku+json; version=3` instead. Verified live:
a dead token returns 400 with application/json but 401 with the
versioned header, which doProbeRequest correctly maps to rejected.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add Mercury, Apollo.io, Deepgram, ClickHouse Cloud, and Langfuse as
access-review connectors. All are API-key, single-tenant providers
(Pattern 3): the key identifies one tenant, so there is no OAuth flow,
picker UI, or bootstrap/helm configuration.
- Mercury: Bearer token, GET /api/v1/users, cursor pagination.
- Apollo.io: x-api-key header, GET /api/v1/users/search (teammates).
- Deepgram: Token scheme; lists members across every project and
dedupes by member_id, unioning per-project scopes.
- ClickHouse Cloud: HTTP Basic (keyId:keySecret); discovers the org
via GET /v1/organizations, then lists its members.
- Langfuse: HTTP Basic (publicKey:secretKey); a base-URL setting
selects the regional cloud host or a self-hosted instance.
Each adds the enum value, migration, GraphQL binding, provider
Registration, a driver with a cassette-driven test, and a brand logo.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The API-key connection transport could present a key as a Bearer token,
an x-api-key header, a custom scheme (SSWS/Token), or HTTP Basic with an
empty password (Cursor). None of these can carry a real password, which
providers such as ClickHouse Cloud (keyId:keySecret) and Langfuse
(publicKey:secretKey) require.
Add a fourth mode, APIKeyBasicAuthUserPass, that base64-encodes the
stored "username:password" credential verbatim into Authorization: Basic.
SetBasicAuth cannot express this -- it re-appends a ":" and corrupts the
credential. The mode is wired generically through the registry and the
create-connector resolver and is mutually exclusive with the other
API-key auth modes.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Requesting a signature only validated that the version was PUBLISHED, so a
signature could be requested on a superseded (older) published version. Reject
versions that are not the document's current published major/minor, and hide
the request button in the console for non-current versions.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
The lockfile was regenerated with npm 11.6.2, which omits optional
platform-specific dependencies for non-host platforms (npm/cli#4828).
This dropped the @rolldown/binding-linux-x64-gnu entry (vite 8 / rolldown
native binding) and broke Linux CI with "Cannot find native binding".
Regenerated with npm ^11.8.0 so all 15 @rolldown/binding-* platforms are
recorded in the lockfile.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Regenerate the stale package-lock and pin patched versions for the
Dependabot-flagged packages (@vitest/browser, form-data, undici,
brace-expansion, js-yaml, esbuild, dompurify, @babel/core, vite,
@opentelemetry/core via posthog-js). Remove the harmful langsmith
override (it forced the vulnerable 0.5.x line) and the zero-effect
lodash-es override.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Upgrade golang.org/x/image from v0.41.0 to v0.43.0 to remediate two
vulnerabilities: CVE-2026-33813 (denial of service via malformed WEBP
parsing, fixed in v0.42.0) and CVE-2026-46602 (missing limit on tile
sizes in x/image/tiff, fixed in v0.43.0). The bump also pulls in
golang.org/x/text v0.38.0 transitively.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce BearerChallengeMiddleware on MCP, Console and Connect GraphQL, Files, and OAuth2 userinfo. Call sites record challenge intent in context via NoteUnauthenticated, NoteInvalidToken, and NoteInsufficientScope; the middleware applies resource_metadata, invalid_token, and insufficient_scope on WriteHeader.
OAuth2 access token middleware flags rejected Bearer tokens for invalid_token challenges. Add Authorizer.ScopesForAction for the scope auth-param.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>