Update webhook events now carry a top-level "updatedFrom" field
alongside "data", containing a full snapshot of the entity as it was
before the update. This lets subscribers diff old vs new state (for
example the prior membership role on user:updated) without tracking
prior state themselves. It is a complete snapshot with the same shape as
"data", not a partial diff, so consumers select whatever fields they
need. The field is omitted for non-update events.
The webhook_data table gains a nullable updated_from JSONB column, and
webhook.InsertUpdateData enqueues both snapshots; InsertData delegates to
it with a nil updatedFrom so non-update callers are unaffected. Each
*:updated emission site snapshots the entity right after load, before
mutation: obligation, third-party, user (org and SCIM flows), document,
document-version, and document-version-approval-quorum. The document
emit helpers gained an optional updatedFrom argument threaded through to
the payload.
For document-version-approval-quorum:updated the snapshot requires an
extra query, so it is now gated behind the same subscription-existence
check the emitter uses: when no subscriber is configured the load is
skipped entirely rather than running (and potentially failing the
approval) for an event nobody receives.
Add integration tests (against a real Postgres, skipped when none is
reachable) covering the updated_from round-trip, the SQL NULL behavior
when no snapshot is provided, and the no-op when no subscription matches,
plus a unit test asserting updatedFrom is omitted from the payload when
absent.
Document the new field in the probod and n8n changelogs and the n8n
README.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Guard ErrorBoundary against a falsy thrown value: track a separate
hasError flag and accept the caught value as unknown, so throwing null,
0, or "" still renders the fallback instead of looping back into the
failing subtree.
Add role="alert" to InlineError and ErrorState so assistive tech
announces errors that appear dynamically after a fetch or query failure.
Fix the RecentUpdatesSection doc example, which showed onRetry={reset}
with @throwOnFieldError even though reset cannot clear a field error;
use a reload and point to the retry table.
Signed-off-by: Émile Ré <emile@probo.com>
Introduce global, page, and section-level error handling for the
compliance portal so a failure is contained at the smallest possible
scope instead of blanking the whole page.
Add a portal-local Relay fetch that throws only request-level errors
(and always redirects on UNAUTHENTICATED) while leaving field-level
errors in the response, so Relay surfaces them at the reading component
through @throwOnFieldError and the nearest boundary. Add a NotFoundError
for node __typename mismatches mapped to a not-found page.
Ship reusable v2 kit primitives (ErrorBoundary, ErrorState, InlineError)
matching the Figma global/local/inline designs, wire the bootstrap and
route boundaries, and demonstrate section and row boundaries on the home
page. Update the error-handling and relay guides accordingly.
Signed-off-by: Émile Ré <emile@probo.com>
The source headers, LICENSE files, and license metadata had drifted
apart. Align the entire project to MIT:
- Convert every source-file header to the MIT text across all comment
styles (Go, TS, TSX, JS, MJS, SQL, CSS, GraphQL, shell), including
SPDX-License-Identifier tags
- Set the root and cookie-banner LICENSE files to the MIT text with a
"MIT License" title line
- Switch the package.json license fields, Docker image label, and
cookie-banner README to MIT
- Update docs and the genmodels header generator accordingly
- Normalize copyright lines to a single format
(Copyright (c) <year(s)> Probo Inc <hello@probo.com>.): unify the
hello@getprobo.com and hello@probo.inc emails to hello@probo.com and
the comma-separated years to a hyphenated range
Genuine third-party references are intentionally left untouched: the
Lucide icon attributions (Lucide is ISC) and the trivy dependency
license allowlist.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
readJsonObject fails when catalog files parse to null, arrays,
or other non-object roots so structural checks cannot be skipped.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Parse and check Claude and Codex marketplace catalogs for
required fields, plugin sources, and npm package name alignment.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Add ISC header to COMPATIBILITY.md, validate Claude marketplace
json, record access-review notes after API success, checksum npm
pack contents, trim dead package.json files entries, and mark
listUsers organization_id as required.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Drop empty agents/ and hooks/ directory placeholders until
those folders have real content.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Ship compliance skills and agent plugin wiring for Claude Code,
Codex, OpenCode, and Cursor via Probo MCP (OAuth 2.0). Includes
access-review and missing-signatures workflows, repo-root Git
marketplaces, validation, and npm release CI.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Forward className on PaginationSkeleton to match the kit skeleton API,
and move the Next arrow to iconEnd per the forward-arrow convention.
Size the updates list skeleton to the page size so the placeholder no
longer jumps when the loaded page renders, and dispose the detail
query on updateId change to avoid a flash of the previous update.
Signed-off-by: Émile Ré <emile@probo.com>
Cover the playground, the both/first/last arrow states that show the
reserved-slot positioning, the optional label, and the skeleton.
Signed-off-by: Émile Ré <emile@probo.com>
Build the public Updates pages in the compliance portal: a
cursor-paginated list of sent mailing-list updates and a detail view
for a single update, replacing the previous stub page.
Add a MailingListUpdate case to the trust API node resolver, guarded so
only SENT updates belonging to the current trust center's mailing list
are exposed, so the detail page can load an update by URL.
Add a Prev/Next Pagination primitive to the v2 UI kit. Page numbers are
omitted because cursor pagination cannot derive an ordinal page index;
each arrow only shows when its page exists while keeping its slot
reserved so a visible arrow never shifts position.
Relocate the shared MailingListUpdateListItem to its own component
folder and wrap each row in a link to the detail page, so both the home
recent-updates section and the list navigate to detail.
Signed-off-by: Émile Ré <emile@probo.com>
Address the review comments on the subprocessors work:
- Reject invalid category/country filter values in the Subprocessors
resolver with an INVALID error instead of relying solely on transport
coercion, so a malformed request fails fast and explicitly.
- Use pgx.StrictNamedArgs in the new distinct facet queries so missing
or extra SQL placeholders stay detectable, matching sibling queries.
- Default a nil ThirdPartyFilter at the service boundary to avoid a nil
dereference in the coredata list/count paths.
- Expose the category group label as an aria heading for assistive tech.
- Add the missing space in the Select "Selected:" story label.
Signed-off-by: Émile Ré <emile@probo.com>
The compliance-portal Subprocessors toolbar needs a dropdown filter and a
search input, which the v2 kit did not provide. Add Select (styled over
Base UI's headless select) and TextField (over Base UI's input) following
the v2 conventions: Base UI primitives, tailwind-variants/lite slots,
flat folders, bundle-safe variants, and paired skeletons. Both map the
Figma semantic colors to the Radix sand/gold token scales.
Signed-off-by: Émile Ré <emile@probo.com>
Add FERPA and PCI DSS framework datasets (controls plus light/dark
logos), their logo components, and register both in the framework
selector so they can be imported like the existing frameworks.
Co-authored-by: Antoine Bouchardy <antoine@getprobo.com>
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
The DocumentVersionSignatureFilter exposed a field named `state` that
actually filters on the signatory's profile state, which was ambiguous
next to the signature `states` field. Rename it to `profileState`
(GraphQL) / `profile_state` (MCP) across the schema, spec, resolvers,
console app, and n8n node for clarity.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Guard the content field against empty strings so adding it in the UI
without a value no longer sends content: "" to the API, which is not
valid ProseMirror content. This matches the existing defaultApproverIds
guard.
Also expose title, classification, and document type on the update
operation. Like content, these edit the current draft version, creating
one from the latest published version when none exists.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Remove the createDraftVersion document operation from the n8n node and
instead expose the document body on the update operation. Updating the
body edits the current draft version, or creates one from the latest
published version when none exists, so a dedicated draft-creation
operation is redundant.
Also correct the content field help text across the document
operations: the API expects a ProseMirror document JSON string, not
markdown.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
The n8n marketplace review flagged the codex node field as using
the wrong identifier format. Update it to match the scoped package
name and the probo node name declared in Probo.node.ts.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Marketplace review flagged Developer Tools and Automation as
unsupported n8n codex category values. Keep Development and use
Utility per review guidance.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
The Probo node implements execute() directly, so n8n never applies
requestDefaults from the node description. Drop the dead block flagged
in marketplace review.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Replace the legacy console.getprobo.com hostnames in the n8n README
and credential default with the current regional Probo URLs.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
The n8n marketplace review requires a README in packages/n8n-node.
Document installation, Probo API credentials, and workflow examples
so users can configure the community node without reading the source.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
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>
Long Mermaid node labels rendered as a single constrained line, which
caused text to be clipped in risk assessment diagrams. Share a Mermaid
render configuration that enables flowchart label wrapping before layout
so nodes expand for multi-line labels.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Sacha Al Himdani <SachaProbo@users.noreply.github.com>
The n8n package could already manage webhook subscriptions through API
CRUD, but had no way to start a workflow when Probo emitted an event. A
user had to drop in the generic Webhook node, create a subscription by
hand, and verify the HMAC signature themselves.
Add a ProboTrigger node that owns the subscription lifecycle: it creates
the subscription on activation pointing at n8n's generated webhook URL,
re-checks and re-registers it if the URL drifts, and deletes it on
deactivation. The webhook handler recomputes the HMAC-SHA256 over the raw
request body and compares it constant-time against the delivered
signature, failing closed when the bytes or headers are absent.
Drop the MEETING_* event choices from the webhook create and update
operations and the CLI event list. They are not part of the backend
WebhookEventType enum, so selecting them only produced API rejections.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Radix can report a pointer down as outside the dialog while a
portaled select or dropdown is open. When the pointer coordinates
still fall within the dialog content bounds, keep the dialog open so
the child popup can close without discarding form state.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
The Organization "Get Many" operation listed all viewer profiles without
filtering by state, then selected each profile's nested organization field.
Inactive profiles (e.g. unaccepted invitations) have no active membership,
so the per-org iam:organization:get authorization check failed and the whole
operation errored.
Filter profiles to ACTIVE state, matching the `prb org list` CLI behavior, so
only organizations the identity is an active member of are fetched.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Bump @n8n/node-cli to 0.37.1 (the `^0.24.1` range was trapping it at
0.24.x), which pulls an updated toolchain and clears the eslint@8,
glob@7, inflight, rimraf@3, @humanwhocodes/*, node-domexception and
uuid@10 deprecation warnings at the source.
Add a glob override (^13.0.6) so rimraf@6 no longer pulls the
deprecated glob@11, and trim the overrides list down to the three that
actually do work (uuid, js-yaml, glob); the rest were redundant or dead.
npm audit reports 0 vulnerabilities.
The upgraded @n8n/eslint-plugin-community-nodes rules flagged two
pre-existing issues in the Probo node, now fixed: handle
continueOnFail() in execute() and throw NodeOperationError instead of
raw Error.
The only remaining warning is @langchain/community, whose entire
package is deprecated upstream and is a hard dependency of
@n8n/ai-utilities.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
Address PR review feedback identified by cubic:
- Re-key CardInset off the Card's resolved padding (shared via context)
instead of size, so insets line up even when padding is decoupled from
size via the padding prop; padding="none" becomes a no-op.
- Give ProboLogo a default aria-label so its role="img" SVG has an
accessible name when used standalone; callers can still override it.
- Make the ProboLogo story render spread args so Storybook controls work.
Signed-off-by: Émile Ré <emile@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>
The popup stacked a CSS border on top of shadow-5, whose first layer is
already a 0 0 0 1px sand-a3 hairline ring, so the edge rendered as two
1px lines. Drop the redundant border and let the shadow provide the
single hairline.
Also transition the scale property instead of transform: Tailwind v4
maps scale-95 to the CSS scale property, so the old transform,opacity
list never animated the scale and the close snapped. Animate scale and
opacity with an explicit 150ms ease-out so open and close are smooth.
Signed-off-by: Émile Ré <emile@probo.com>
Soften the popup border to the translucent sand-a2 token and inset the
separator (mx-3) so it lines up with item text instead of bleeding to
the panel edges, lightening it to sand-a3. Both now track the Figma
alpha hairlines and keep their dark-mode values via the Radix alpha
scale.
Also modernize the existing arbitrary-variant classes in this file to
Tailwind's shorthand syntax (data-[x]: -> data-x:, origin-[var(...)] ->
origin-(...)).
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>
The solid color rules in the Button, IconButton, and Badge variant
matrices had no highContrast condition, so a high-contrast solid element
matched both the base rule and the high-contrast rule. With
tailwind-variants/lite (no tailwind-merge) both classes are emitted, and
a static text-white from the base wins over the theme-aware text step.
That looked fine in light mode but left illegible white text on the
light step-12 background once dark mode was enabled.
Gate the base solid rules on highContrast:false so the self-contained
high-contrast rules are authoritative. The tinted variants are left as
is: their step-11/step-12 text both flip with the theme, so they are
never illegible.
Signed-off-by: Émile Ré <emile@probo.com>
Round out the button family with the two navigational siblings described
in the UI guide: Anchor renders a styled <a> for external links and Link
renders a react-router link for in-app navigation, both sharing Button's
tv styles. Add an `active` look variant so nav items can carry a
persistent selected state without forking structure.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Card component from the Probo Radix UI Figma: a <div>
container with size 1-5 (padding + radius), the surface/classic/ghost
variants, and an interactive hover/active affordance. Add a CardInset
part that bleeds content (e.g. a cover image) to the card edges,
reading the card size from context so it negates the matching
padding. Includes a paired CardSkeleton. Styling uses
tailwind-variants/lite.
Signed-off-by: Émile Ré <emile@probo.com>
Implement the Dropdown Menu family from the Probo Radix UI Figma on
top of Base UI's Menu primitive, exported as flat Dropdown* parts:
Dropdown (root), DropdownTrigger, DropdownPopup, DropdownItem (with
shortcut and accent/error color), DropdownCheckboxItem,
DropdownRadioGroup/RadioItem, DropdownGroup/GroupLabel,
DropdownSeparator, and DropdownSubmenu/SubmenuTrigger.
The popup carries size (1-2), variant (solid/soft highlight), and
highContrast to its items through a small context. Styling uses
tailwind-variants/lite with data-[highlighted] selectors; item
shortcuts inherit the contrast color on highlight.
Signed-off-by: Émile Ré <emile@probo.com>