The OAuth token and consent UIs fall back to the raw scope string when
a label is missing from scopeLabels. Add read and manage labels for
v1:resource-alias so it matches other API scopes.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Third-party compliance report PDFs were rejected above 10MB in the
upload dialog. Increase the client-side Dropzone limit and enforce the
same cap on the backend file validator.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
The compliance page rendered a Slack connector as "Connected" whenever
any SLACK connector existed for the org — including one created for an
access review with no channel configured — and only offered Disconnect,
which deletes the shared connector row.
Distinguish channel-configured connections from unconfigured ones and
let users (re)connect in place to pick a channel without first
disconnecting, reusing the existing reconnect-with-union-scopes flow via
connector_id.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Mirror the risks refactor (c78a713): colocate routes.ts, split the
detail layout query so each child route owns its Loader + Page, rename
tabs/*Tab to resource folders with *Page, move dialogs into
_components/, and extract ThirdPartyRow with its own fragment.
Remove outlet context data passing and deprecated
loaderFromQueryLoader. Delete the monolithic ThirdPartyGraph hook,
colocating each GraphQL operation with its consumer: the create
mutation in CreateThirdPartyDialog (now useMutation + useToast) and
the third-party list queries in ThirdPartiesCell and
ThirdPartiesMultiSelectField (now useQueryLoader + usePreloadedQuery).
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Add a resource-oriented set of webhook events for the document
lifecycle. Each event carries the document plus only the sub-resource
it concerns (version, signature or approval).
Events:
- document.created / updated / archived / unarchived / deleted
- document.version.created / updated / published / rejected / deleted
- document.version.signature.requested / signed / cancelled
- document.version.approval.requested / approved / rejected / voided
Wires the new types through the migration, Go enum, GraphQL schema,
CLI, n8n nodes and the console webhooks settings UI.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Add the frameworks (Compliance), security commitments, trusted-by, and
recent updates sections to the home page, plus a site-wide "Powered by
Probo" footer. Frameworks, references, and updates are relay-driven; the
security commitments section uses placeholder POJO data until a backend
exists.
Keep generic primitives in the v2 kit (ProboLogo, a faint Text tone, and
a soft Card variant with padding="none") and place the product-specific
cards under the app's components folder, composing the base Card. Resolve
each framework's themed logo in the graph via Framework.themedLogoUrl,
mirroring the trust center logo resolver.
Signed-off-by: Émile Ré <emile@probo.com>
Fix the valid issues raised in the scaffold review.
UI kit: the Button loading state now replaces only the leading icon
instead of dropping the label, Button consumes the `active` variant so
it no longer leaks onto the DOM, and every v2 skeleton sets aria-hidden
after the prop spread so a consumer cannot override it.
@probo/relay: guard the caller-supplied onCompleted/onError callbacks so
a throwing callback still settles the awaitable mutation promise instead
of leaving it pending.
compliance-portal: normalize external website hrefs and read hostname
via URL.hostname, add a localized catch-all not-found route, and widen
the .gitattributes glob so colocated __generated__ artifacts at any depth
are marked generated.
Docs: correct the forms guide (Base UI passes plain values, Zod v3
flatten API), spread the child fragment in the permissions example, and
drop references to v2 components that do not exist in the ui guide.
Signed-off-by: Émile Ré <emile@probo.com>
Add a TrustCenter.themedLogoUrl Relay live resolver that reads the
light and dark logos from the graph and subscribes to the system
prefers-color-scheme media query, returning the dark logo (falling
back to the light one) when the OS prefers dark.
The TopBar now selects this single field instead of pulling both logo
URLs and branching on useSystemTheme, keeping the theme-to-URL logic in
the graph where any component can reuse it.
Signed-off-by: Émile Ré <emile@probo.com>
DropdownGroupLabel renders Base UI's Menu.GroupLabel, which requires a
Menu.Group ancestor. The user menu rendered the label directly in the
popup, so opening it threw a missing MenuGroupContext error. Wrap the
email label in a DropdownGroup.
Signed-off-by: Émile Ré <emile@probo.com>
The dark-mode script set the .dark class once at load. Subscribe to the
prefers-color-scheme media query so the theme also updates while the page
is open when the OS appearance changes.
Signed-off-by: Émile Ré <emile@probo.com>
TextSkeleton and HeadingSkeleton baked w-full into their shared base.
Under tailwind-variants/lite (no tailwind-merge) that collided with the
caller's width class and won by stylesheet order, so every skeleton
ignored its width: bars stretched full in column contexts and collapsed
in flex rows.
Drop w-full from the base so the single caller width applies, matching
ButtonSkeleton/AvatarSkeleton. Size the compliance-portal TopBar and
Hero skeleton bars to roughly match their English copy, and add a rule
so skeleton widths are revisited when en-US strings change.
Signed-off-by: Émile Ré <emile@probo.com>
Add a synchronous head script to the compliance portal that sets the
.dark class on <html> when the OS prefers a dark scheme, so the v2 Radix
theme resolves to dark values before first paint with no flash. Declare
a color-scheme meta so native UI matches the active scheme.
Signed-off-by: Émile Ré <emile@probo.com>
Replace the remaining hard-coded UI copy in the top bar, user menu, home
hero, and the documents/subprocessors/updates/requests pages with i18next
keys, and supply the en-US and fr-FR catalog entries for them. The home
hero title uses value interpolation rather than string concatenation so
word order stays correct across locales.
Signed-off-by: Émile Ré <emile@probo.com>
Wire i18next into the compliance portal with a custom backend built on
import.meta.glob, so each _locales/*.json becomes its own lazily loaded
chunk keyed by a namespace derived from the folder path. The active
language is resolved from the browser, collapsing any fr*/en* tag to
fr-FR/en-US with en-US as the ultimate fallback; fallbackLng then only
covers individual missing keys.
Add an app-level default namespace catalog and switch the documents
page title to a translation key to exercise the lazy-loading path.
Signed-off-by: Émile Ré <emile@probo.com>
On a hard reload the root route briefly showed a generic centered pulse
(PageSkeleton) while the layout chunk downloaded, then snapped to the
topbar skeleton. Point the root Fallback at MainLayoutSkeleton so the
chunk-load fallback matches the loader's own skeleton and the transition
is seamless. PageSkeleton is now unused and removed.
Signed-off-by: Émile Ré <emile@probo.com>
Build the Trust Center landing hero and the nav page headers on a shared
HeaderBand shell. The home page loads its own query and feeds the Hero
(org headline, description, and contact info) via a colocated fragment;
the Documents, Subprocessors, Updates, and Data Requests pages use a
PageHeader (size-7 title, optional count, inline actions). Switch the
layout body to the grey sand-2 canvas so the white header bands read as
surfaces on top.
Split the header UI into focused components rather than one
over-configurable Hero: HeaderBand owns the band, Hero the landing
content, PageHeader the nav-page content. Add a hostname helper under
lib/url and standardize the 1024px container on the max-w-5xl token.
The Documents/Subprocessors filter/search/tabs toolbars and item counts
are deferred until the matching v2 components and queries exist.
Signed-off-by: Émile Ré <emile@probo.com>
Build the Trust Center top navigation in a Relay-wired layout route. The
portal now mounts a Relay environment and provider, and the root layout
loads a query whose fragment feeds the TopBar: brand, ghost-pill nav with
an active state from the router, and a guest "Get Access" button versus an
authenticated user menu. Placeholder section routes keep the nav links and
active state functional until real pages land.
Wire the Relay tagged-template transform via @rolldown/plugin-babel and
set the router basename to the /trust/{slug} path prefix so the app
resolves under its served path.
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>
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>
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>
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>
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 trust node query previously accepted a String and resolved both GIDs
and slugs through one field, which forced the frontend to lose the ID
type guarantee. Restore node(id: ID!) as a strict GID lookup and add a
dedicated aliasedNode(alias: String!) that parses a GID first and falls
back to slug resolution before delegating to Node.
Inline the former nodeByGID switch directly into Node and drop the helper
file. Point the trust DocumentPage query at aliasedNode so slug-or-ID URLs
keep working.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Update the compliance page alias field and list items to call the
generic setResourceAlias and removeResourceAlias mutations instead of
the trust-center-specific ones, and drop the now-removed audit alias
field usage.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The audit alias resolver returned raw service errors. Log them
and return gqlutils.Internal like other resolvers in the file.
Remove-only users could edit the alias field to a new value that
was never saved. Reset local state when set permission is missing,
and catch mutation rejections on blur.
Sitemap generation appended audit report file IDs without
deduplication, which could emit duplicate document URLs when
multiple audits share the same report file.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Document, file, and audit rows now navigate to /documents/<alias>
when an alias is set, falling back to the resource ID otherwise.
The document page query accepts a string ID to support both forms.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Adds an editable alias column to the documents, files, and audits
tables so operators can set and clear aliases from the console.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Replace the duplicated per-workspace eslint.config.mjs files in
apps/console, apps/trust, packages/ui, and packages/eslint-config with
one root eslint.config.mjs that reuses the shared @probo/eslint-config
rule sets and scopes them per directory. Linting now runs from the repo
root, so pin the type-checked project service root and broaden the
import-x resolver to every workspace tsconfig (the #/* aliases live in
each app's tsconfig.app.json).
Drop the now-redundant per-package lint scripts and lint-only devDeps,
and add a root lint script that runs eslint over the four dirs with
multithreading restored via --concurrency auto, then lints n8n-node
through a direct workspace call. packages/n8n-node keeps its own
external preset.
Collapse the redundant lint-js -> npm-lint Makefile chain into a single
lint-js target and update the make docs accordingly.
Signed-off-by: Émile Ré <emile@probo.com>
Wrap the usePreloadedQuery calls whose explicit type arguments pushed
them past the 120-character limit, and merge the two duplicate
@probo/helpers imports in FrameworkControlPage into one. These were
flagged by reviewdog on the dependency-upgrade PR.
Signed-off-by: Émile Ré <emile@probo.com>
Relay no longer needs a single merged schema.graphql: each project in
relay.config.json now reads the split graphql/*.graphql files directly
via `schema` (base.graphql) plus `schemaExtensions`. gqlgen already
consumed the split files, so the merge step only fed Relay and the
optional IDE GraphQL extension.
Remove the merge machinery (contrib/merge-graphql-schema.sh, the
RELAY_SCHEMAS make rules, and the gitignore entry) and drop the
graphql-config files (apps/trust/graphql.config.yml and the root
package.json graphql field); the Relay extension provides schema-aware
language features from relay.config.json on its own.
relay-compiler keeps generated artifacts in sync (stale ones are
removed automatically), so the relay npm script just runs the local
relay-compiler and the make target delegates to it.
Signed-off-by: Émile Ré <emile@probo.com>
The linter's project service ignores the generated Relay artifacts,
so the untyped usePreloadedQuery call left viewer as an error type and
triggered no-unsafe-argument/member-access errors. Pass the explicit
query generic like the sibling audit-log page does.
Drop the redundant GraphQLError cast on the revoke error handler since
formatError already accepts the callback's error type.
Signed-off-by: Émile Ré <emile@probo.com>
Bump React 19.2, Relay 21, React Router 8, Vite 8 with
@vitejs/plugin-react 6, ESLint 10, GraphQL 17, TypeScript 6,
@types/node 24, and Tailwind 4.3 across the workspaces.
vite-plugin-react 6 (Vite 8) no longer runs Babel, so the Relay
tagged-template transform now runs through @rolldown/plugin-babel
in the console and trust Vite configs.
Relay 21 ships first-party types and enables the ambiguous-alias
check by default; disable that flag to preserve existing queries
and add explicit usePreloadedQuery type arguments where the new
types no longer infer the operation. TypeScript 6 deprecations and
stricter inference are addressed in tsconfigs and call sites.
Keep n8n-node on ESLint 9 and eslint-plugin-react on 7.37.5, the
newest releases compatible with their toolchains.
Signed-off-by: Émile Ré <emile@probo.com>
Local storage, IndexedDB, and cache storage have no expiry yet
persist until explicitly cleared, so a missing max-age should read
as "persistent", not "session" (the latter only fits cookies and
session storage, which end with the session or tab).
Thread the tracker type through humanizeSeconds (helpers) and
humanizeDuration (cookie-banner, with a localized persistent label)
and pass it at every console and banner call site. The consent
record query now selects trackerType so its duration column can
make the same distinction. This mirrors the Go HumanizedDuration
helper that already renders these types as persistent.
Signed-off-by: Émile Ré <emile@probo.com>
The category property on the tracker pattern detail page was
read-only text. Wire in the existing MoveToCategorySelect and the
moveTrackerPatternToCategory mutation so a pattern can be recategorized
directly from its detail view, matching the table-row behaviour.
Signed-off-by: Émile Ré <emile@probo.com>
Surface every CookieSource value in the console: the trackers page
filter was missing the HTTP option and the source badge helper had no
EXTENSION case, so HTTP-sourced rows could not be filtered and
extension-sourced rows rendered the raw enum string.
On the backend, the mark-first-party verdict now blanks the stale
description on both the catalog row and its uncategorised org tracker
patterns. A terminal non-third-party row keeps no vendor link, so a
description naming the (now-cleared) vendor would be misleading; the
mapping worker only copies descriptions into empty rows and never
clears them, so clearing is done explicitly here.
Signed-off-by: Émile Ré <emile@probo.com>
Let users create, list, and revoke manual bearer tokens from
/me/oauth-tokens, scoped to their identity rather than an
organization. Manual tokens store a null client_id and are
authorized with a self-manage IAM policy.
Wire Connect GraphQL on Identity (list, create, revoke), add
console UI with scoped create flow and credentials dialog, and
cover the flow in e2e tests. Fix list pagination ordering and
keep the Relay connection in sync after create.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Register v1 API scopes in coredata, advertise them in OIDC discovery
and protected-resource metadata, show them on the consent screen, and
enforce scope-to-action mapping in the IAM Authorizer before policy
evaluation.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
Replace the immediate per-document approval email and the manual
"send signing notifications" action with a single debounced worker that
batches pending requests per recipient and organization.
The worker (go.gearno.de/kit/worker) polls on an interval (default 5m)
and claims one (organization, recipient) group at a time, sending one
consolidated signing email and/or one approval email per recipient/org
that lists every document awaiting their signature or approval. The
claim is a conditional UPDATE that doubles as concurrency-safe dedup, so
several workers never email the same group twice.
Each request is notified once it has been pending past the debounce
delay (default 15m), then reminded at 1x, 2x and 3x the reminder
interval (default 1 day) after the previous email, after which it stops.
New last_notified_at and notification_count columns on signatures and
approval decisions drive the debounce, the widening reminder cadence and
the four-email cap.
Email copy lists each document with its title, type and a deep link to
the employee page. Removed the inline approval-on-publish email, the
SendSigningNotifications service method/mutation/MCP tool, its IAM action,
and the related console UI and n8n operation.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
OIDC login dropped organization_id before the provider redirect, so
callbacks with an existing matching root session never created an org
child session. Persist organization_id in OIDC state, open the child
session on callback, and forward the parameter from the sign-in UI.
Signed-off-by: Bryan Frimin <bryan@probo.com>