zuplo 6.70.70 → 6.70.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/docs/ai-gateway/getting-started.mdx +2 -1
  2. package/docs/ai-gateway/integrations/ai-sdk.mdx +17 -0
  3. package/docs/ai-gateway/introduction.mdx +5 -5
  4. package/docs/ai-gateway/providers.mdx +2 -0
  5. package/docs/analytics/access-and-entitlements.md +71 -0
  6. package/docs/analytics/overview.md +63 -0
  7. package/docs/analytics/reference/metrics-glossary.md +105 -0
  8. package/docs/analytics/reference/url-parameters.md +66 -0
  9. package/docs/analytics/shared-controls.md +121 -0
  10. package/docs/analytics/tabs/agents.md +88 -0
  11. package/docs/analytics/tabs/consumers.md +73 -0
  12. package/docs/analytics/tabs/graphql.md +77 -0
  13. package/docs/analytics/tabs/mcp.md +80 -0
  14. package/docs/analytics/tabs/origins.md +82 -0
  15. package/docs/analytics/tabs/requests.md +96 -0
  16. package/docs/articles/ci-cd-github/basic-deployment.mdx +10 -1
  17. package/docs/articles/ci-cd-github/deploy-and-test.mdx +14 -1
  18. package/docs/articles/ci-cd-github/local-testing.mdx +3 -1
  19. package/docs/articles/ci-cd-github/pr-preview-environments.mdx +36 -4
  20. package/docs/articles/custom-ci-cd-github.mdx +11 -2
  21. package/docs/articles/monetization/api-access.mdx +184 -0
  22. package/docs/articles/monetization/meters.mdx +4 -4
  23. package/docs/articles/monetization/monetization-policy.md +4 -1
  24. package/docs/articles/monetization/private-plans.md +3 -4
  25. package/docs/articles/monetization/stripe-integration.md +9 -0
  26. package/docs/articles/monetization/subscription-lifecycle.md +12 -11
  27. package/docs/articles/monorepo-deployment.mdx +20 -2
  28. package/docs/cli/deploy.mdx +32 -0
  29. package/docs/cli/deploy.partial.mdx +32 -0
  30. package/docs/concepts/api-keys.md +2 -2
  31. package/docs/dev-portal/zudoku/components/callout.mdx +11 -18
  32. package/docs/dev-portal/zudoku/configuration/search.md +36 -0
  33. package/docs/dev-portal/zudoku/configuration/site.md +38 -0
  34. package/docs/dev-portal/zudoku/customization/colors-theme.mdx +51 -40
  35. package/docs/errors/rate-limit-exceeded.mdx +30 -3
  36. package/docs/policies/_index.md +2 -0
  37. package/docs/policies/data-loss-prevention-inbound/doc.md +116 -0
  38. package/docs/policies/data-loss-prevention-inbound/intro.md +15 -0
  39. package/docs/policies/data-loss-prevention-inbound/schema.json +220 -0
  40. package/docs/policies/data-loss-prevention-outbound/doc.md +116 -0
  41. package/docs/policies/data-loss-prevention-outbound/intro.md +18 -0
  42. package/docs/policies/data-loss-prevention-outbound/schema.json +220 -0
  43. package/docs/programmable-api/background-dispatcher.mdx +6 -8
  44. package/docs/programmable-api/zone-cache.mdx +1 -1
  45. package/docs/rate-limiting/combining-policies.mdx +293 -0
  46. package/docs/rate-limiting/dynamic-rate-limiting.mdx +240 -0
  47. package/docs/rate-limiting/getting-started.mdx +339 -0
  48. package/docs/rate-limiting/how-it-works.md +225 -0
  49. package/docs/rate-limiting/monitoring-and-troubleshooting.mdx +243 -0
  50. package/docs/{articles → rate-limiting}/per-user-rate-limits-using-db.mdx +39 -27
  51. package/package.json +4 -4
  52. package/docs/concepts/rate-limiting.md +0 -246
@@ -52,6 +52,190 @@ curl \
52
52
  --header "Authorization: Bearer $ZAPI_KEY"
53
53
  ```
54
54
 
55
+ ## Bucket monetization configuration
56
+
57
+ Each bucket has an optional `MonetizationConfiguration` that holds bucket-wide
58
+ behavior — multi-subscription support, plan display order, plan-level overrides,
59
+ and the default payment grace period. The configuration is read by the runtime
60
+ and the Developer Portal; it is not stored in OpenMeter.
61
+
62
+ ### Read
63
+
64
+ ```shell
65
+ curl \
66
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
67
+ --header "Authorization: Bearer $ZAPI_KEY"
68
+ ```
69
+
70
+ When no configuration row exists for the bucket, the endpoint returns a default
71
+ body with `multipleSubscriptionsEnabled: false`, an empty `planOrder`, empty
72
+ `planSettings`, and `maxPaymentOverdueDays: 3`.
73
+
74
+ ### Upsert
75
+
76
+ ```shell
77
+ curl \
78
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
79
+ --request PUT \
80
+ --header "Authorization: Bearer $ZAPI_KEY" \
81
+ --header "Content-Type: application/json" \
82
+ --data @- << EOF
83
+ {
84
+ "multipleSubscriptionsEnabled": false,
85
+ "planOrder": ["free", "starter", "pro", "enterprise"],
86
+ "planSettings": {
87
+ "pro": { "visiblePhases": ["default"] }
88
+ },
89
+ "maxPaymentOverdueDays": 7
90
+ }
91
+ EOF
92
+ ```
93
+
94
+ | Field | Type | Description |
95
+ | ------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | `multipleSubscriptionsEnabled` | `boolean` | Stored on the bucket. Reserved for future multi-subscription rules; today the Developer Portal create-subscription path enforces a single active subscription per customer regardless of this flag. |
97
+ | `planOrder` | `string[]` | Ordered list of plan keys; drives pricing-page sort and upgrade/downgrade direction during plan changes |
98
+ | `planSettings` | `object` | Per-plan overrides keyed by plan key. The supported sub-key today is `visiblePhases` — an array of phase keys that should appear on the pricing page |
99
+ | `maxPaymentOverdueDays` | `integer` | Bucket-default payment grace period. Must be ≥ 0. Defaults to `3` when not set |
100
+
101
+ The request body must include at least one of these fields. All four fields are
102
+ optional in the request — the upsert preserves any field you don't send.
103
+
104
+ `planOrder` is consumed when a customer changes plans through the Developer
105
+ Portal: a target plan whose index is greater than or equal to the current plan's
106
+ index is treated as an upgrade (immediate timing); a lower index is treated as a
107
+ downgrade (next-billing-cycle timing). Plans not listed in `planOrder` default
108
+ to upgrade timing.
109
+
110
+ `maxPaymentOverdueDays` is the lowest-precedence default for the payment grace
111
+ period. See
112
+ [Subscription and payment validation](./monetization-policy.md#subscription-and-payment-validation)
113
+ for the full precedence chain (customer metadata → plan metadata → bucket
114
+ configuration → built-in default).
115
+
116
+ ### Delete
117
+
118
+ ```shell
119
+ curl \
120
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/monetization-configuration \
121
+ --request DELETE \
122
+ --header "Authorization: Bearer $ZAPI_KEY"
123
+ ```
124
+
125
+ After deletion, GET returns the default body again.
126
+
127
+ ## Stripe setup and billing readiness
128
+
129
+ Most users connect Stripe through the
130
+ [Zuplo Portal](./stripe-integration.md#connecting-your-stripe-account). For
131
+ automated provisioning — CI scripts, infrastructure-as-code, or self-hosted
132
+ control planes — the same flow is available via these API endpoints.
133
+
134
+ ### Install the Stripe app
135
+
136
+ Connect a Stripe account to a bucket and create the default billing profile in
137
+ one call:
138
+
139
+ ```shell
140
+ curl \
141
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe \
142
+ --request POST \
143
+ --header "Authorization: Bearer $ZAPI_KEY" \
144
+ --header "Content-Type: application/json" \
145
+ --data @- << EOF
146
+ {
147
+ "apiKey": "rk_test_...",
148
+ "name": "Stripe Billing Profile",
149
+ "taxEnabled": false,
150
+ "taxEnforced": false,
151
+ "country": "US"
152
+ }
153
+ EOF
154
+ ```
155
+
156
+ The endpoint validates the Stripe key prefix against the bucket's environment:
157
+
158
+ - Working-copy and preview buckets accept `sk_test_*` or `rk_test_*`
159
+ - Production buckets accept `sk_live_*` or `rk_live_*`
160
+
161
+ The response returns the installed `appId`. The endpoint fails with a
162
+ `409 Conflict` if a Stripe app is already installed for the bucket.
163
+
164
+ ### Read the current Stripe setup
165
+
166
+ ```shell
167
+ curl \
168
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe \
169
+ --header "Authorization: Bearer $ZAPI_KEY"
170
+ ```
171
+
172
+ Returns the connected Stripe app summary and the billing profiles linked to it.
173
+
174
+ ### Create an additional billing profile
175
+
176
+ To attach more billing profiles to the same Stripe app:
177
+
178
+ ```shell
179
+ curl \
180
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/setup/stripe/$STRIPE_APP_ID/billing-profile \
181
+ --request POST \
182
+ --header "Authorization: Bearer $ZAPI_KEY" \
183
+ --header "Content-Type: application/json" \
184
+ --data @- << EOF
185
+ {
186
+ "name": "EU Billing Profile",
187
+ "taxEnabled": true,
188
+ "taxEnforced": false,
189
+ "country": "DE"
190
+ }
191
+ EOF
192
+ ```
193
+
194
+ ### Check billing readiness
195
+
196
+ A lightweight check for tooling that gates deploys on Stripe being connected:
197
+
198
+ ```shell
199
+ curl \
200
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/billing-readiness \
201
+ --header "Authorization: Bearer $ZAPI_KEY"
202
+ ```
203
+
204
+ Response:
205
+
206
+ ```json
207
+ {
208
+ "hasStripeApp": true,
209
+ "stripeAppId": "app_01H...",
210
+ "hasDefaultBillingProfile": true,
211
+ "defaultBillingProfileId": "bp_01H..."
212
+ }
213
+ ```
214
+
215
+ Use this in setup wizards to gate the UI on whether Stripe is connected.
216
+
217
+ ### Update a connected app
218
+
219
+ Rotate the Stripe key on an existing app, or update its name and metadata:
220
+
221
+ ```shell
222
+ curl \
223
+ https://dev.zuplo.com/v3/metering/$BUCKET_ID/apps/$APP_ID \
224
+ --request PUT \
225
+ --header "Authorization: Bearer $ZAPI_KEY" \
226
+ --header "Content-Type: application/json" \
227
+ --data @- << EOF
228
+ {
229
+ "type": "stripe",
230
+ "name": "Stripe Billing Profile",
231
+ "secretAPIKey": "rk_test_..."
232
+ }
233
+ EOF
234
+ ```
235
+
236
+ The same key-prefix validation applies — a live key is rejected on a
237
+ non-production bucket and vice versa.
238
+
55
239
  ## API Reference
56
240
 
57
241
  For complete API operations, see the API Reference documentation:
@@ -58,10 +58,10 @@ carries the quantity:
58
58
  `subject` identifies _who_ consumed the subscription's entitlements on this
59
59
  request — typically the API key's consumer name, the end-user id, or another
60
60
  stable per-actor identifier. It is **not** the subscription id (use the
61
- `subscription` field for that) and is not used to route billing. Its purpose is
62
- to let you break down usage within a subscription so you can see which key,
63
- user, or agent drove the consumption — a single subscription will commonly emit
64
- events with many different `subject` values. See
61
+ `subscription` field for that) and does not route billing. Its purpose is to let
62
+ you break down usage within a subscription so you can see which key, user, or
63
+ agent drove the consumption — a single subscription will commonly emit events
64
+ with many different `subject` values. See
65
65
  [Monetization Policy](./monetization-policy.md) for how usage is recorded.
66
66
 
67
67
  :::
@@ -145,7 +145,10 @@ below it:
145
145
 
146
146
  1. **Customer metadata** — `zuplo_max_payment_overdue_days` on the customer
147
147
  2. **Plan metadata** — `zuplo_max_payment_overdue_days` on the plan
148
- 3. **Default** — `3` days
148
+ 3. **Bucket configuration** —
149
+ [`maxPaymentOverdueDays`](./api-access.mdx#bucket-monetization-configuration)
150
+ on the bucket's monetization configuration
151
+ 4. **Default** — `3` days
149
152
 
150
153
  Set the value to `0` to block requests immediately when payment is overdue.
151
154
 
@@ -99,10 +99,9 @@ Save the returned `id` — you need it to publish and invite users.
99
99
 
100
100
  :::note
101
101
 
102
- The plan `id` is a 26-character ULID (regex
103
- `^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$`), separate from the human-friendly
104
- `key` you set on creation. The publish, invite, and other plan-scoped endpoints
105
- require the `id`, not the `key`.
102
+ The plan `id` is a 26-character ULID. It's distinct from the human-friendly
103
+ `key` field. Use the `id` (not the `key`) when calling `/publish` and
104
+ `/plan-invites`.
106
105
 
107
106
  :::
108
107
 
@@ -45,6 +45,15 @@ specifically Customers, Checkout Sessions, Customer Portal Sessions, Invoices,
45
45
  and Tax Calculations. See
46
46
  [What Zuplo creates in Stripe](#what-zuplo-creates-in-stripe) for the full list.
47
47
 
48
+ :::tip
49
+
50
+ To script the connection — for CI, infrastructure-as-code, or self-hosted
51
+ control planes — use the
52
+ [Stripe setup API endpoints](./api-access.mdx#stripe-setup-and-billing-readiness)
53
+ instead of the Portal flow.
54
+
55
+ :::
56
+
48
57
  ### Test mode vs. live mode
49
58
 
50
59
  Connect with a Stripe **test** key (`sk_test_...`) first to validate your
@@ -67,8 +67,9 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions \
67
67
  -H "Authorization: Bearer {API_KEY}" \
68
68
  -H "Content-Type: application/json" \
69
69
  -d '{
70
- "plan": { "key": "pro" },
71
- "customerId": "01J9ZX2A8R0K8H6VG2C1A0K3WP"
70
+ "plan": { "key": "pro", "version": 1 },
71
+ "customerKey": "user_external_id",
72
+ "timing": "immediate"
72
73
  }'
73
74
  ```
74
75
 
@@ -216,14 +217,15 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscri
216
217
  -H "Content-Type: application/json" \
217
218
  -d '{
218
219
  "timing": "immediate",
219
- "plan": { "key": "enterprise" }
220
+ "plan": { "key": "enterprise", "version": 1 }
220
221
  }'
221
222
  ```
222
223
 
223
224
  `timing` accepts `"immediate"`, `"next_billing_cycle"`, or an RFC 3339
224
225
  timestamp. Optional fields include `startingPhase`, `name`, `description`,
225
- `metadata`, `alignment`, and `billingAnchor`. To preview the proration credit
226
- before committing, call
226
+ `metadata`, `alignment`, and `billingAnchor`. The response includes both the
227
+ closed-out (`current`) and newly-started (`next`) subscriptions. To preview the
228
+ proration credit before committing, call
227
229
  `POST /v3/metering/{bucketId}/subscriptions/{subscriptionId}/change/estimate-credit`
228
230
  with the same body.
229
231
 
@@ -310,9 +312,7 @@ behavior does not apply.
310
312
  curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscriptionId}/cancel \
311
313
  -H "Authorization: Bearer {API_KEY}" \
312
314
  -H "Content-Type: application/json" \
313
- -d '{
314
- "timing": "next_billing_cycle"
315
- }'
315
+ -d '{ "timing": "next_billing_cycle" }'
316
316
  ```
317
317
 
318
318
  `timing` controls when the cancellation takes effect:
@@ -341,9 +341,10 @@ curl -X POST https://dev.zuplo.com/v3/metering/{bucketId}/subscriptions/{subscri
341
341
  -H "Authorization: Bearer {API_KEY}"
342
342
  ```
343
343
 
344
- This removes the pending cancellation. The subscription continues as normal. For
345
- a subscription whose period has already ended, create a new subscription on the
346
- same plan instead.
344
+ This removes the pending cancellation. The subscription continues as normal.
345
+
346
+ If the subscription has already ended, create a new subscription rather than
347
+ restoring the old one.
347
348
 
348
349
  ## Subscriptions per customer
349
350
 
@@ -114,7 +114,9 @@ jobs:
114
114
  run: npm install
115
115
 
116
116
  - name: Deploy to Zuplo
117
- run: npx zuplo deploy --api-key "$ZUPLO_API_KEY"
117
+ run:
118
+ npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "${{
119
+ github.ref_name }}"
118
120
  env:
119
121
  ZUPLO_API_KEY: ${{ secrets.ZUPLO_API_KEY }}
120
122
  ```
@@ -171,8 +173,12 @@ jobs:
171
173
  - name: Deploy to Zuplo
172
174
  id: deploy
173
175
  shell: bash
176
+ env:
177
+ # The PR's source branch — not the pull/<number>/merge ref that
178
+ # pull_request events check out
179
+ ENVIRONMENT: ${{ github.head_ref }}
174
180
  run: |
175
- OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" 2>&1)
181
+ OUTPUT=$(npx zuplo deploy --api-key "$ZUPLO_API_KEY" --environment "$ENVIRONMENT" 2>&1)
176
182
  echo "$OUTPUT"
177
183
  DEPLOYMENT_URL=$(echo "$OUTPUT" | grep -oP 'Deployed to \K(https://[^ ]+)')
178
184
  echo "url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
@@ -230,6 +236,18 @@ jobs:
230
236
  --wait
231
237
  ```
232
238
 
239
+ :::caution
240
+
241
+ The deploy step passes `--environment` with the PR's source branch name
242
+ (`github.head_ref`). Workflows triggered by `pull_request` check out the PR
243
+ merge ref (`refs/pull/<number>/merge`), so without `--environment` the CLI names
244
+ the environment after that ref instead of your branch — and any other deploy of
245
+ the same branch creates a second environment with a different URL. See
246
+ [PR Preview Environments](./ci-cd-github/pr-preview-environments.mdx) for
247
+ details.
248
+
249
+ :::
250
+
233
251
  ### Secrets and environment variables
234
252
 
235
253
  Store your Zuplo API key as a GitHub Actions secret:
@@ -122,6 +122,38 @@ zuplo deploy --project my-project
122
122
  zuplo deploy --project my-project --environment my-env-name
123
123
  ```
124
124
 
125
+ ### Deploying from CI/CD
126
+
127
+ Without `--environment`, the CLI names the environment after the current git
128
+ branch. CI systems usually check out a detached HEAD, in which case the CLI
129
+ resolves the branch from the remote branches that contain the checked-out
130
+ commit. Two cases break this inference:
131
+
132
+ - On GitHub Actions `pull_request` events, the checkout is the pull request
133
+ merge ref (`refs/pull/<number>/merge`) — a commit that doesn't exist on any
134
+ branch — so the environment is named after that ref instead of your branch.
135
+ - When the checked-out commit exists on more than one branch, the first match
136
+ wins, which may not be the branch that triggered the build.
137
+
138
+ Always pass `--environment` explicitly in CI so that every trigger deploys the
139
+ same, predictably named environment:
140
+
141
+ ```bash
142
+ # GitHub Actions: github.head_ref is the source branch on pull_request
143
+ # events; github.ref_name is the branch on push events
144
+ zuplo deploy --project my-project --environment "$BRANCH_NAME"
145
+ ```
146
+
147
+ Deploying the same environment name always updates the same environment and
148
+ keeps its URL stable across deploys. This matters whenever an external system
149
+ must match the URL exactly — an OIDC token audience, a webhook registration, or
150
+ an allowlist. Capture the URL from the deploy output (`Deployed to
151
+ https://...`) rather than constructing it from the branch name: the URL
152
+ hostname uses a normalized, truncated form of the environment name plus a
153
+ unique identifier. See
154
+ [Branch-Based Deployments](../articles/branch-based-deployments.mdx) for the
155
+ naming rules.
156
+
125
157
  ## Polling timeout
126
158
 
127
159
  By default, the deploy command polls the status of the deployment every second
@@ -25,6 +25,38 @@ zuplo deploy --project my-project
25
25
  zuplo deploy --project my-project --environment my-env-name
26
26
  ```
27
27
 
28
+ ### Deploying from CI/CD
29
+
30
+ Without `--environment`, the CLI names the environment after the current git
31
+ branch. CI systems usually check out a detached HEAD, in which case the CLI
32
+ resolves the branch from the remote branches that contain the checked-out
33
+ commit. Two cases break this inference:
34
+
35
+ - On GitHub Actions `pull_request` events, the checkout is the pull request
36
+ merge ref (`refs/pull/<number>/merge`) — a commit that doesn't exist on any
37
+ branch — so the environment is named after that ref instead of your branch.
38
+ - When the checked-out commit exists on more than one branch, the first match
39
+ wins, which may not be the branch that triggered the build.
40
+
41
+ Always pass `--environment` explicitly in CI so that every trigger deploys the
42
+ same, predictably named environment:
43
+
44
+ ```bash
45
+ # GitHub Actions: github.head_ref is the source branch on pull_request
46
+ # events; github.ref_name is the branch on push events
47
+ zuplo deploy --project my-project --environment "$BRANCH_NAME"
48
+ ```
49
+
50
+ Deploying the same environment name always updates the same environment and
51
+ keeps its URL stable across deploys. This matters whenever an external system
52
+ must match the URL exactly — an OIDC token audience, a webhook registration, or
53
+ an allowlist. Capture the URL from the deploy output (`Deployed to
54
+ https://...`) rather than constructing it from the branch name: the URL
55
+ hostname uses a normalized, truncated form of the environment name plus a
56
+ unique identifier. See
57
+ [Branch-Based Deployments](../articles/branch-based-deployments.mdx) for the
58
+ naming rules.
59
+
28
60
  ## Polling timeout
29
61
 
30
62
  By default, the deploy command polls the status of the deployment every second
@@ -50,8 +50,8 @@ After successful validation, the policy populates `request.user`:
50
50
  - `request.user.data` contains the consumer's metadata (plan, customerId, etc.)
51
51
 
52
52
  This lets downstream policies and handlers make authorization decisions, apply
53
- per-consumer [rate limits](./rate-limiting.md), or forward identity to your
54
- backend.
53
+ per-consumer [rate limits](../rate-limiting/how-it-works.md), or forward
54
+ identity to your backend.
55
55
 
56
56
  :::note
57
57
 
@@ -193,24 +193,17 @@ Or pass any React node as the `icon` to override the default for the chosen `typ
193
193
  ## Customize colors
194
194
 
195
195
  Each callout type is driven by a single CSS variable that determines the icon, border tint, and
196
- background tint via `color-mix`. Override any of them in your theme's `customCss` to recolor a type
197
- globally:
198
-
199
- ```ts title="zudoku.config.ts"
200
- export default {
201
- theme: {
202
- customCss: `
203
- :root {
204
- --callout-tip: oklch(0.65 0.18 160);
205
- --callout-sparkles: oklch(0.6 0.22 320);
206
- }
207
- .dark {
208
- --callout-tip: oklch(0.78 0.18 160);
209
- --callout-sparkles: oklch(0.75 0.2 320);
210
- }
211
- `,
212
- },
213
- };
196
+ background tint via `color-mix`. Override any of them in your stylesheet to recolor a type globally:
197
+
198
+ ```css title=styles.css
199
+ :root {
200
+ --callout-tip: oklch(0.65 0.18 160);
201
+ --callout-sparkles: oklch(0.6 0.22 320);
202
+ }
203
+ .dark {
204
+ --callout-tip: oklch(0.78 0.18 160);
205
+ --callout-sparkles: oklch(0.75 0.2 320);
206
+ }
214
207
  ```
215
208
 
216
209
  All available variables:
@@ -167,3 +167,39 @@ your [Dev Portal Configuration](./overview.md):
167
167
  // ...
168
168
  }
169
169
  ```
170
+
171
+ ### Customizing Inkeep
172
+
173
+ Any other [base settings](https://docs.inkeep.com/cloud/ui-components/common-settings/base) can be
174
+ set alongside the required fields, for example `filters` to scope results or `transformSource` to
175
+ customize how sources are displayed.
176
+
177
+ You can also pass
178
+ [`searchSettings`](https://docs.inkeep.com/cloud/ui-components/common-settings/search),
179
+ [`aiChatSettings`](https://docs.inkeep.com/cloud/ui-components/common-settings/ai-chat), and
180
+ `modalSettings` to customize the respective parts of the search experience. They are merged with
181
+ Zudoku's defaults and passed through to Inkeep as-is, so any option Inkeep supports can be used —
182
+ including ones added after this Dev Portal version was released.
183
+
184
+ For example, to categorize results into tabs based on their URL:
185
+
186
+ ```typescript
187
+ {
188
+ // ...
189
+ search: {
190
+ type: "inkeep",
191
+ // ...required fields from above
192
+ transformSource: (source) => {
193
+ if (!source.url.includes("/blog/")) return source;
194
+ return { ...source, tabs: [...(source.tabs ?? []), "Blog"] };
195
+ },
196
+ searchSettings: {
197
+ tabs: [["All", { isAlwaysVisible: true }], "Blog"],
198
+ },
199
+ aiChatSettings: {
200
+ aiAssistantName: "My Assistant",
201
+ },
202
+ },
203
+ // ...
204
+ }
205
+ ```
@@ -127,6 +127,44 @@ export const NotFound = () => (
127
127
 
128
128
  ## Layout
129
129
 
130
+ ### Collapsible Sidebar
131
+
132
+ The navigation sidebar is collapsible by default. A small toggle button on the sidebar's right
133
+ border lets users hide and reveal it. Configure the behavior under `site.sidebar`:
134
+
135
+ ```tsx title=zudoku.config.tsx
136
+ {
137
+ site: {
138
+ sidebar: {
139
+ collapsible: true, // default: true. Set to false to disable the toggle entirely.
140
+ toggleVisibility: "always", // "always" (default) or "hover" — show button only when hovering the sidebar's right edge
141
+ togglePosition: "bottom", // "top", "center", or "bottom" (default)
142
+ },
143
+ }
144
+ }
145
+ ```
146
+
147
+ For finer vertical placement, override the `--sidebar-toggle-y` CSS variable in your stylesheet:
148
+
149
+ ```css
150
+ :root {
151
+ --sidebar-toggle-y: 30%;
152
+ }
153
+ ```
154
+
155
+ The toggle button carries `aria-expanded="true"` when the sidebar is open and `"false"` when
156
+ collapsed. Combine it with the `[data-sidebar-toggle]` selector to position the button differently
157
+ per state:
158
+
159
+ ```css
160
+ [data-sidebar-toggle][aria-expanded="true"] {
161
+ --sidebar-toggle-y: 20%;
162
+ }
163
+ [data-sidebar-toggle][aria-expanded="false"] {
164
+ --sidebar-toggle-y: 80%;
165
+ }
166
+ ```
167
+
130
168
  ### Banner
131
169
 
132
170
  Add a banner message to the top of the page: