Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf960d1
fix(settings): point setup audit fix hints and links at screens that …
Makisuo Oct 16, 2026
a3f1a26
fix(ingest): give server-side SDK snippets the private ingest key
Makisuo Oct 16, 2026
58e9162
fix(settings): show audit log resource and source columns, retry fail…
Makisuo Oct 16, 2026
4d0acf1
fix(billing): show an error with retry when the billing customer fail…
Makisuo Oct 16, 2026
31b5224
fix(settings): stop notifications from defaulting to on when the load…
Makisuo Oct 16, 2026
e72c5c3
fix(settings): flag the policy simulator while the escalation draft i…
Makisuo Oct 16, 2026
ee7d1f9
fix(settings): confirm before applying ClickHouse schema or adding th…
Makisuo Oct 16, 2026
48023dd
fix(settings): correct the Claude Code MCP config path and keep secre…
Makisuo Oct 16, 2026
575cc90
fix(settings): use the shared admin hook in Organization and Members
Makisuo Oct 16, 2026
edd9acc
fix(settings): gate member-rejected actions and say why
Makisuo Oct 16, 2026
3f30463
fix(billing): format billing control amounts in the spend model's cur…
Makisuo Oct 16, 2026
22fc5fc
fix(settings): redirect the orphaned /developer page to settings
Makisuo Oct 16, 2026
3dbd777
fix(settings): report a drifted table's unresolved drift instead of c…
Makisuo Oct 17, 2026
2646990
fix(settings): never show a stale simulation or mark an unsaved draft…
Makisuo Oct 17, 2026
b36fad2
feat(account): add an email preferences tab under a new notifications…
Makisuo Oct 17, 2026
4e282ff
feat(settings): regroup the nav into workspace, access, data and aler…
Makisuo Oct 17, 2026
4a3b6ce
chore(settings): point emails and docs at the renamed settings tabs
Makisuo Oct 17, 2026
d0f6cca
feat(settings): add a wide width variant to settings sections
Makisuo Oct 17, 2026
3150a9c
feat(settings): add a sticky save bar, draft hook and unsaved changes…
Makisuo Oct 17, 2026
1e1064b
feat(settings): add an admin-only notice that names an admin to ask
Makisuo Oct 17, 2026
d19d438
fix(settings): keep saved draft edits until the refetch catches up
Makisuo Oct 17, 2026
ef778b0
Merge remote-tracking branch 'origin/main' into feat/settings-redesig…
Makisuo Oct 17, 2026
4fa2c0e
fix(settings): release committed drafts when the save normalized the …
Makisuo Oct 17, 2026
4ba009e
chore: clear knip findings on the settings foundation
Makisuo Oct 17, 2026
462ddbf
fix(settings): keep edits made during an in-flight save dirty
Makisuo Oct 17, 2026
9a743c7
fix(settings): keep the unsaved-changes guard armed while a save is p…
Makisuo Oct 17, 2026
4950e5d
fix(settings): keep a reverted edit dirty while its save is in flight
Makisuo Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/ai/src/platform/genai-spans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ export interface ModelCallAttempts {
}

/** Which attempt a model-call span is; only a retry past the cap carries it. */
export const MODEL_CALL_ATTEMPT_ATTR = "maple_ai.model_call.attempt"
const MODEL_CALL_ATTEMPT_ATTR = "maple_ai.model_call.attempt"

/**
* Build a provider's language model so that every `streamText` call — the only call Maple makes, and
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/alerting/alert-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ The preview sends nothing. Use it to check that a threshold would not have fired

## Create a rule over the API

Rules are available at `/v2/alerts/rules` on `https://api.maple.dev` (`https://api.eu.maple.dev` for EU organizations). Use an API key (`maple_ak_…`) from **Settings → API Keys**. Creating, updating, deleting and testing a rule requires the `alerts:write` scope and the org admin role.
Rules are available at `/v2/alerts/rules` on `https://api.maple.dev` (`https://api.eu.maple.dev` for EU organizations). Use an API key (`maple_ak_…`) from **Settings → Keys**. Creating, updating, deleting and testing a rule requires the `alerts:write` scope and the org admin role.

```bash
curl -X POST https://api.maple.dev/v2/alerts/rules \
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/iac/alchemy.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ That is the whole integration. Each request becomes a server span, Effect logs a

### 4. Deploy

Create an API key in **Settings → API Keys**. An org admin's key with full access is simplest; see [Authentication](#authentication) for the minimum scopes. Then deploy:
Create an API key in **Settings → Keys**. An org admin's key with full access is simplest; see [Authentication](#authentication) for the minimum scopes. Then deploy:

```bash
MAPLE_API_KEY=maple_ak_… alchemy deploy
Expand Down
4 changes: 2 additions & 2 deletions apps/landing/src/content/docs/integrations/gcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ When you create a connection in Maple, "The Google Cloud project acme-prod is al
| **Waiting for the first entry** for more than 20 minutes | Either nothing was logged that passes the filter, or the sink can't publish. Run the `gcloud logging write` command the band shows. If the entry does not arrive within a minute, open **Configure** and run the setup script again. |
| **Rejected**: "The Pub/Sub subscription wraps each entry in an envelope Maple can't read." | The subscription was changed to deliver wrapped messages. Run the setup script again: it resets the subscription. Entries sent meanwhile are lost. |
| **Rejected**: "Maple could not store an entry just now." | Nothing to do. Pub/Sub retries the entry for up to a day, and the status returns to **Receiving** with the next accepted entry. |
| **Rejected**: "This Maple organization is over its plan limit, so Maple refuses new logs." | Raise the plan limit under **Settings → Billing**. Pub/Sub retries refused entries for up to a day. |
| **Rejected**: "This Maple organization is over its plan limit, so Maple refuses new logs." | Raise the plan limit under **Settings → Billing & usage**. Pub/Sub retries refused entries for up to a day. |
| A workload's logs appear twice | The sink forwards its application output and the workload also sends its logs over OpenTelemetry. In **Configure**, choose **Replace filter**, leave its runtime unticked under [Log filter](#log-filter) and run the script again. |
| A workload's stdout and stderr are missing | Application output is not forwarded by default. Send it over OpenTelemetry, or tick its runtime under [Log filter](#log-filter) in **Configure** and run the script again. |

Expand All @@ -542,7 +542,7 @@ When you create a connection in Maple, "The Google Cloud project acme-prod is al
| "3 of 46 metric queries failed, first ..." | The rest were stored. The failed metrics miss those minutes, and Maple retries in 5 minutes. |
| "3 of 46 metric queries were not read in full" | The project, folder or organization holds more series than one read takes. Connect the folders or projects of a folder or organization as separate connections. For a single project, write to support. |
| "Reading the metrics took longer than two minutes." | Maple reads the same minutes again, which can store part of them twice. If it repeats, connect the folders or projects of a folder or organization separately. For a single project, write to support. |
| "Metrics are paused: this Maple organization is over its plan limit." | Raise the plan limit under **Settings → Billing**. Maple tries again in an hour. |
| "Metrics are paused: this Maple organization is over its plan limit." | Raise the plan limit under **Settings → Billing & usage**. Maple tries again in an hour. |
| "Maple could not store the metrics it read just now." | Nothing to do. Maple reads the same minutes again. |
| "Google denied the resource listing for project acme-prod." | Run the setup script again: it grants Cloud Asset Viewer. Metrics are unaffected. |
| "The project holds more than 10,000 resources, so the resource list is incomplete." | Metrics are unaffected. For a full list, connect the folders or projects of a folder or organization separately. For a single project, write to support. |
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Use the base URL of your organization's [region](/docs/reference/regions). A key

## Authentication

Create an API key in the dashboard under **Settings → API Keys**, or with `POST /v2/api_keys` using an existing key. Send it as a Bearer token on every request:
Create an API key in the dashboard under **Settings → Keys**, or with `POST /v2/api_keys` using an existing key. Send it as a Bearer token on every request:

```bash
curl https://api.maple.dev/v2/services \
Expand Down
6 changes: 3 additions & 3 deletions apps/landing/src/content/docs/reference/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Maple uses two kinds of credential. **Ingest keys** send telemetry. **API keys**
| ------------------ | ----------- | ---------------------------------------- | ---------------------------------------------- | ---------------------------- |
| Public ingest key | `maple_pk_` | **Settings → Ingestion** | Send telemetry | Yes |
| Private ingest key | `maple_sk_` | **Settings → Ingestion** | Send telemetry | No |
| API key | `maple_ak_` | **Settings → API Keys** | REST API and MCP server, limited by its scopes | No |
| API key | `maple_ak_` | **Settings → Keys** | REST API and MCP server, limited by its scopes | No |
| MCP key | `maple_ak_` | **Settings → MCP** | MCP server only | No |
| OAuth token | | Issued to an MCP client when you sign in | MCP server only | No |

Expand Down Expand Up @@ -44,7 +44,7 @@ The literal key `MAPLE_TEST` is accepted and returns `200`, but the data is disc

## API keys

Create API keys under **Settings → API Keys**, or with `POST /v2/api_keys`. When you create one you choose:
Create API keys under **Settings → Keys**, or with `POST /v2/api_keys`. When you create one you choose:

| Field | Options |
| ---------------------------- | ----------------------------------------------------- |
Expand All @@ -64,7 +64,7 @@ API requests are limited to 600 per 60 seconds per key. See [Limits](/docs/refer

An MCP client can authenticate in three ways:

- **An API key** from **Settings → API Keys**, sent as `Authorization: Bearer maple_ak_…`.
- **An API key** from **Settings → Keys**, sent as `Authorization: Bearer maple_ak_…`.
- **An MCP key** from **Settings → MCP**. It has full access to the MCP tools and is rejected by the REST API.
- **OAuth**, for clients that support the MCP authorization flow. The client discovers Maple's authorization server from the endpoint URL, registers itself, and opens a browser sign-in. You pick the organization on the consent screen. Access tokens last 1 hour and are refreshed automatically. The client has to sign in again after 30 days without a refresh, and 90 days after the first sign-in at the latest. OAuth tokens only work on the MCP server.

Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ maple use remote # optional: stop auto-detecti
maple services --since 1h
```

`maple auth login` opens your browser, and you approve the CLI for one organization. The CLI then holds an [API key](/docs/reference/authentication#api-keys) with full access that expires after 90 days. It appears under **Settings → API Keys** with the description "Created by maple auth login". Run `maple auth login` again when it expires, and `maple auth logout` to revoke it.
`maple auth login` opens your browser, and you approve the CLI for one organization. The CLI then holds an [API key](/docs/reference/authentication#api-keys) with full access that expires after 90 days. It appears under **Settings → Keys** with the description "Created by maple auth login". Run `maple auth login` again when it expires, and `maple auth logout` to revoke it.

Every command except `maple query` works in remote mode. The server commands (`start`, `stop`, `reset`, `checkpoint`, `restore`, `archive`, `schema`) always act on the local store. Which region your organization is in is on [Regions](/docs/reference/regions).

Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/reference/ingest.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Checks run roughly in the order below, so a request fails on the first one it tr
| `415` | `ingest_unsupported_media_type` | `@maple/ingest/UnsupportedMediaType` | Unknown `Content-Type` or `Content-Encoding` | No. Fix the exporter config |
| `400` | `ingest_bad_request` | `@maple/ingest/BadRequest` | Invalid gzip, a body that is not valid OTLP, or a malformed product event, session event or replay header | No |
| `400` | `ingest_replay_body_not_gzip` | `@maple/ingest/ReplayBodyNotGzip` | A session replay chunk that is not a gzip stream | No |
| `402` | `ingest_plan_limit_reached` | `@maple/ingest/PlanLimitReached` | No active subscription, or the plan's limit is reached | No. Check **Settings → Billing** |
| `402` | `ingest_plan_limit_reached` | `@maple/ingest/PlanLimitReached` | No active subscription, or the plan's limit is reached | No. Check **Settings → Billing & usage** |
| `429` | `ingest_queue_throttled` | `@maple/ingest/OrgQueueThrottled` | Your organization's ingest queue is full. Nothing was written | Yes, after 1 s |
| `429` | `ingest_export_lane_full` | `@maple/ingest/ExportLaneBackpressure` | The write path for your organization is backed up. Nothing was written | Yes, after 2 s |
| `503` | `ingest_unavailable` | `@maple/ingest/ServiceUnavailable` | Key lookup or another dependency is temporarily unavailable | Yes, after 5 s |
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/reference/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Use the endpoint of your organization's [region](/docs/reference/regions). The e

Any MCP client that speaks Streamable HTTP can connect. **Settings → MCP** in the dashboard shows the endpoint for your region and ready-made configuration for common clients. There are two ways to authenticate. Both are described in more detail on [Authentication](/docs/reference/authentication).

**API key.** Create a key under **Settings → MCP** or **Settings → API Keys** and pass it as a Bearer token:
**API key.** Create a key under **Settings → MCP** or **Settings → Keys** and pass it as a Bearer token:

```json
{
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/reference/regions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Keys do not cross regions. An ingest key or API key created in one region is rej

## Find your organization's region

- **Settings → Organization** shows a **Data region** row: "United States" or "European Union (Frankfurt)".
- **Settings → General** shows a **Data region** row: "United States" or "European Union (Frankfurt)".
- The organization switcher in the sidebar shows `· US` or `· EU` after the organization name.
- The dashboard host you use is the region: `app.maple.dev` is US, `app.eu.maple.dev` is EU.

Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/content/docs/reference/retention.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Individual spans, log lines and data points cannot be read once they pass these
| Data | Kept for |
| ------------------------------------------------------------ | -------------------- |
| Alert rule checks (each evaluation, with its observed value) | 365 days |
| Organization audit log (**Settings → Audit Log**) | 6 years (2,190 days) |
| Organization audit log (**Settings → Audit log**) | 6 years (2,190 days) |

## Maple Local

Expand Down
18 changes: 13 additions & 5 deletions apps/web/src/components/account/account-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BellIcon,
ComputerIcon,
EnvelopeIcon,
FingerprintIcon,
Expand All @@ -17,22 +18,24 @@ export const accountTabValues = [
"passkeys",
"connections",
"sessions",
"email-preferences",
] as const
export type AccountTab = (typeof accountTabValues)[number]

export const accountTabLabels: Record<AccountTab, string> = {
profile: "Profile",
emails: "Email Addresses",
emails: "Email addresses",
password: "Password",
"two-factor": "Two-Factor Auth",
"two-factor": "Two-factor auth",
passkeys: "Passkeys",
connections: "Connected Accounts",
sessions: "Active Sessions",
connections: "Connected accounts",
sessions: "Active sessions",
"email-preferences": "Email preferences",
} satisfies Record<AccountTab, string>

/**
* Landing tab when `/account` is opened with no `?tab=`. Unlike `/settings`, every account tab is
* visible to any signed-in user, so this is a single value rather than a preference order — there
* visible to any signed-in user, so this is a single value rather than a preference order: there
* is no permission filtering that could make it unavailable.
*/
export const DEFAULT_ACCOUNT_TAB: AccountTab = "profile"
Expand Down Expand Up @@ -63,6 +66,11 @@ export const accountNavSections: ReadonlyArray<NavShellSection<AccountTab>> = [
{ id: "sessions", label: accountTabLabels.sessions, icon: ComputerIcon },
],
},
{
id: "notifications",
title: "Notifications",
items: [{ id: "email-preferences", label: accountTabLabels["email-preferences"], icon: BellIcon }],
},
]

/** Which tab `/account` should render: the requested one when it is a real tab, else the default. */
Expand Down
29 changes: 29 additions & 0 deletions apps/web/src/components/settings/admin-only-notice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest"
import { adminOnlyNoticeText, memberDisplayName } from "./admin-only-notice"

describe("memberDisplayName", () => {
it("prefers the full name, then the identifier", () => {
expect(memberDisplayName({ firstName: "Ada", lastName: "Lovelace", identifier: "ada@x.dev" })).toBe(
"Ada Lovelace",
)
expect(memberDisplayName({ firstName: "Ada", lastName: null, identifier: "ada@x.dev" })).toBe("Ada")
expect(memberDisplayName({ firstName: "", lastName: null, identifier: "ada@x.dev" })).toBe(
"ada@x.dev",
)
})

it("returns nothing when no admin is known", () => {
expect(memberDisplayName(undefined)).toBeUndefined()
expect(memberDisplayName({ firstName: null, lastName: null, identifier: "" })).toBeUndefined()
})
})

describe("adminOnlyNoticeText", () => {
it("names the admin to ask when one is known", () => {
expect(adminOnlyNoticeText("billing", "Ada")).toBe("Only admins can change billing. Ask Ada.")
})

it("omits the ask when no admin name resolved", () => {
expect(adminOnlyNoticeText("billing", undefined)).toBe("Only admins can change billing.")
})
})
Loading
Loading