Pattern merges and adoptions that stay entirely within the
uncategorised category never change the visitor-facing snapshot.
Only trigger ensureDraftVersionForBanner when a merge group
belongs to a consent category.
Signed-off-by: Émile Ré <emile@getprobo.com>
Translations are no longer part of the version snapshot, so the
banner_description text must be validated at write time to ensure
the {{cookie_policy_link}} placeholder is present. Without it
the cookie policy URL silently disappears from the rendered banner.
Signed-off-by: Émile Ré <emile@getprobo.com>
Decouple detection from the banner's active state so admins
can discover cookies before making the banner visible. The
client now starts the detector even when the config endpoint
returns 404, and the detector stops itself if the report
endpoint returns 404 (wrong or deleted banner ID).
Signed-off-by: Émile Ré <emile@getprobo.com>
The uncategorised category is an admin-side inbox for detected
cookies and should not be part of the visitor-facing consent
contract. Filter it out of snapshots so changes to uncategorised
patterns no longer trigger version bumps.
Signed-off-by: Émile Ré <emile@getprobo.com>
Translation changes are cosmetic, not consent-contract changes,
so they should not trigger a version bump. Translations are now
loaded live from the database at serve time instead of being
frozen in the snapshot.
Signed-off-by: Émile Ré <emile@getprobo.com>
A central snapshot-equality guard in ensureDraftVersion now returns
the latest version unchanged when the candidate snapshot matches it,
so no-op admin saves no longer force visitors to re-consent. Per-
operation short-circuits in UpdateCookieBanner, UpdateCookieCategory,
UpdateCookiePattern, DeleteCookiePattern, MoveCookiePatternToCategory,
ReorderCookieCategory, and UpsertCookieBannerTranslation skip the row
update and version bump when nothing visitor-facing changes (excluded
patterns, identical values, identical translation JSON).
Rank is now treated as admin-only metadata: buildSnapshot sorts
categories by (Kind weight, ID byte order) instead of relying on the
implicit rank-driven slice order, and ReorderCookieCategory no longer
calls ensureDraftVersionForBanner. Default banners keep their
visitor-facing order (insertion order matches Kind+ID); banners with
admin-customised ranks see a one-time order shift to insertion order
on the next snapshot rebuild.
Reusable equality helpers (Ptr generic + JSON canonicalisation) move
to a new pkg/equal package; snapshotsEqual stays in service.go as the
documented chokepoint for visitor-identical snapshot comparison.
Signed-off-by: Émile Ré <emile@getprobo.com>
The previous fix prevented unintentional clearing of
`privacyPolicyUrl` and `maxAgeSeconds` by skipping the field
when its value was falsy, but this also removed the user's
ability to explicitly clear an existing value.
Move both fields into an `Additional Fields` collection so we
can distinguish between "not provided" (skip) and "provided
empty" (clear), matching the existing pattern in
`vendor/update.operation.ts`.
Signed-off-by: Émile Ré <emile@getprobo.com>
Avoid clearing existing values when optional fields are left at their
defaults: only send privacyPolicyUrl/maxAgeSeconds when truthy, filter
empty entries from gcmConsentTypes, and constrain consentExpiryDays to
positive integers on create.
Signed-off-by: Émile Ré <emile@getprobo.com>
Adds four new resources (cookieBanner, cookieCategory,
cookiePattern, cookieConsentRecord) covering all mutations
and queries from the cookie banner GraphQL resolvers.
Signed-off-by: Émile Ré <emile@getprobo.com>
Show the origin of each cookie pattern (Script vs Pre-existing)
as a badge with a tooltip in the cookies configuration table.
Signed-off-by: Émile Ré <emile@getprobo.com>
Test that excluded defaults to false on create, can be set
to true via update, and can be toggled back to false.
Signed-off-by: Émile Ré <emile@getprobo.com>
Add excluded boolean to the MCP CookiePattern schema and
UpdateCookiePatternInput. Expose it in CLI cookie-pattern
view, list, and update commands.
Signed-off-by: Émile Ré <emile@getprobo.com>
Expose excluded on the CookiePattern type and accept it as
an optional input on UpdateCookiePatternInput so the console
can toggle pattern exclusion inline.
Signed-off-by: Émile Ré <emile@getprobo.com>
Filter excluded and non-exact patterns in SQL when loading
patterns for the pattern analysis worker. Both merge group
building and uncategorised adoption only see non-excluded
exact patterns, so excluded patterns are preserved as
punch-out overrides.
Signed-off-by: Émile Ré <emile@getprobo.com>
In ReportDetectedCookies, silently skip cookies that match an
excluded pattern instead of recording them. Filter excluded
patterns in SQL when building version snapshots so they never
appear in the published banner config. Add Excluded field to
UpdateCookiePatternRequest.
Signed-off-by: Émile Ré <emile@getprobo.com>
Adds an `excluded` boolean column to the cookie_patterns table
so operators can mark patterns to be omitted from the consent
banner without deleting them. Includes the migration, struct
field, updated SQL queries, and filter support.
Signed-off-by: Émile Ré <emile@getprobo.com>
The inline int type assertion silently dropped int64 and float64
values already supported by Attrs.getInt.
Signed-off-by: Émile Ré <emile@getprobo.com>
- Validate __typename in all list command pagination callbacks
- Serialize PostHogConsent=false as explicit false (not nil)
- Fix maxAgeSeconds factory to only include when value is an int
- Rename cookie-banner versions to latest-version subcommand
Signed-off-by: Émile Ré <emile@getprobo.com>
Wire cookiebanner.Service into the MCP resolver and expose 24 tools
covering full CRUD, activation, versioning, translations, and consent
record queries with pagination and filtering support.
Signed-off-by: Émile Ré <emile@getprobo.com>
The version state field returns uppercase enum values (DRAFT, PUBLISHED)
and "analytics" is a default category slug created with every banner.
Signed-off-by: Émile Ré <emile@getprobo.com>
Cover CRUD, activation, versioning, translations, categories, patterns,
RBAC, and tenant isolation with factory helpers for test data creation.
Signed-off-by: Émile Ré <emile@getprobo.com>
The Add Person dialog wrapped its form content in a plain <div>
instead of DialogContent. When the form is tall enough, the
dialog extends beyond the viewport with no way to scroll,
cutting off the contract end date field.
Replace the raw <div> with DialogContent which provides
overflow-y-auto and a maxHeight constraint, matching the
pattern used by all other dialogs in the codebase.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Émile Ré <nemile.re@gmail.com>
Users authenticated via Google/Microsoft OIDC or magic link previously
relied on a fall-through in the password-only org check. Make the rule
explicit so SSO-only users can access password-only organizations
without being bounced to the password login form they cannot satisfy.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
The activate() method was only called when consent existed (from
cookie or API). When there was no prior consent, observeAndActivate()
never ran, so visual elements with data-cookie-consent were left
without placeholders. Build default consent data from the config's
consent_mode and always call activate() at the end of load().
Also consolidate activateElements/addPlaceholders into
observeAndActivate to avoid duplicate DOM traversals.
Signed-off-by: Émile Ré <emile@getprobo.com>
Expand the dimensional property allowlist with padding, box-sizing,
and positioning properties (position, top, right, bottom, left,
inset) so placeholders match the size of absolutely or sticky
positioned elements. Fall back to getComputedStyle for height when
no explicit value is found from inline styles or HTML attributes.
Signed-off-by: Émile Ré <emile@getprobo.com>
The adoptUncategorisedPatterns method loaded all patterns for a
banner then filtered in Go. This adds a CookiePatternFilter
(match_type + cookie_category_id) and wires it into
LoadAllByCookieBannerID so the two targeted loads only fetch
the rows they need.
Signed-off-by: Émile Ré <emile@getprobo.com>
When a cookie pattern already exists for a banner, the insert
conflict caused a continue that silently dropped the detected
cookie instead of linking it to the existing pattern. Load the
existing pattern to obtain its ID and proceed with cookie
insertion.
Signed-off-by: Émile Ré <emile@getprobo.com>
Replace the ad-hoc patternAnalysisTask struct with coredata.CookieBanner
as the worker type parameter, matching the pattern used by other workers
(esign, accessreview). SQL methods move back to coredata on *CookieBanner.
Signed-off-by: Émile Ré <emile@getprobo.com>
Use InsertIfNotExists instead of Insert with error check, since
a unique-violation aborts the PostgreSQL transaction even when caught.
Signed-off-by: Émile Ré <emile@getprobo.com>
Move the DurationInput component from the console app into @probo/ui
for reuse, add duration formatting helpers to @probo/helpers, and
update pattern merge to group by both category ID and prefix.
Signed-off-by: Émile Ré <emile@getprobo.com>
- Fix DurationInput fallback unit from "minutes" to "seconds" and add
seconds as a selectable unit to prevent silent duration inflation
- Use parseFloat instead of parseInt for duration input to preserve
fractional values
- Scope prefix merge groups by category ID to prevent cross-category
merging
- Relink cookies and delete exact patterns even when prefix pattern
already exists
- Prefer exact matches and longest prefix in pattern selection query
- Fix wrong error type in GetCookiePattern (ErrCookiePatternNotFound)
- Handle singular/plural in humanizeSeconds fallback branch
Signed-off-by: Émile Ré <emile@getprobo.com>
Replace the free-form duration TEXT column with a nullable
max_age_seconds INTEGER on both cookies and cookie_patterns
tables. The SDK detector now sends raw seconds instead of
humanized strings, eliminating locale-dependent comparisons
in the pattern merge worker. Humanization happens at display
time in the widget and console UI.
Signed-off-by: Émile Ré <emile@getprobo.com>
The previous algorithm split on the first separator only, producing
overly broad prefixes (e.g. ph_ for ph_phc_abc123). Replace with a
greedy longest-shared-prefix approach that finds the longest
separator-boundary prefix shared by 3+ exact patterns, avoiding
false merges across unrelated cookie families.
Signed-off-by: Émile Ré <emile@getprobo.com>
The console now manages CookiePattern entities instead of raw
Cookie rows. The frontend queries cookiePatterns on each category
and uses createCookiePattern, updateCookiePattern,
deleteCookiePattern, and moveCookiePatternToCategory mutations.
The entire Cookie GraphQL surface (type, connection, mutations,
inputs, payloads, resolvers, Go types) is removed since the
backing struct already lost description and cookieCategoryID.
Signed-off-by: Émile Ré <emile@getprobo.com>
Set pattern_analysis_requested_at on all existing banners so the worker
runs once per banner on release and merges any existing prefix groups.
Signed-off-by: Émile Ré <emile@getprobo.com>