Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Documentation files are located in the `./docs` directory.
This directory contains the docs as-is, the deployment of the docs are handled by [apps/docs](./apps/docs). A docusaurus project that syncs the docs content to its directory. When writing docs, the root `./docs` directory is the source of truth.

See [`docs/AGENTS.md`](./docs/AGENTS.md) for the docs contribution scope (we only actively maintain `docs/wg/**` and `docs/reference/**`).
When linking docs to editor pages, prefer **universal routing** (`https://grida.co/_/<path>`). See `docs/wg/platform/universal-docs-routing.md`.

## `/crates/*`

Expand Down
9 changes: 9 additions & 0 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ Other folders under `/docs` are **not actively managed**.
- Unless you have a specific task, **avoid editing** content outside `docs/wg/**` and `docs/reference/**`.
- Do not edit generated artifacts under `/apps/docs/docs/**`.

## Universal routing (linking to editor pages)

When writing or updating **user-facing docs**, prefer **universal routing** links for any “open this page in the editor” instruction.

- **Use production URLs**: links should start with `https://grida.co`.
- **Use the universal prefix**: use `https://grida.co/_/<path>` instead of tenant-specific canonical paths.
- Example: `https://grida.co/_/connect/channels`
- **If you add a new user-facing page** that should be linkable from docs, make sure it’s registered in **universal routing** so the `/_/…` alias resolves correctly. See `docs/wg/platform/universal-docs-routing.md`.

## Conventions

### `_history/` directories (unlisted docs)
Expand Down
6 changes: 3 additions & 3 deletions docs/forms/respondent-email-notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ Practically, this means:
### How to enable respondent email notifications

1. Open your **Form** in the Grida editor.
2. In the left sidebar, click **Connect**.
3. Click **Channels**.
2. In the left sidebar, click [**Connect**](https://grida.co/_/connect).
3. Click [**Channels**](https://grida.co/_/connect/channels).
Comment on lines +28 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Universal routing links in docs — verify they work for readers without auth context.

These links use the new /_/ universal routing prefix (https://grida.co/_/connect and https://grida.co/_/connect/channels). Per the PR's routing spec, universal routes require context resolution (org/project), and the UniversalRoutePicker page redirects unauthenticated users to sign-in.

For documentation readers who aren't logged in or land on this page externally, these links will redirect to sign-in rather than showing the referenced UI. Consider whether plain text navigation steps (the previous approach) or a mix of both would be more user-friendly for docs that are publicly accessible.

🤖 Prompt for AI Agents
In `@docs/forms/respondent-email-notifications.md` around lines 28 - 29, The docs
currently use universal routing links (/ _/connect and / _/connect/channels)
which will redirect unauthenticated readers to sign-in via UniversalRoutePicker;
change these to public-friendly navigation instructions by replacing the
universal-prefixed URLs with either the previous plain-text steps (e.g., "In the
left sidebar, click Connect → Channels") or with non-universal absolute paths
that don’t use the /_/ prefix, and optionally include both a universal link (for
logged-in users) plus a short parenthetical note telling unauthenticated readers
that the link requires sign-in and to instead follow the plain navigation steps.

4. Under **Email Notifications**, find **Respondent email notifications**.
5. Toggle **Enable** on.
6. Click **Save**.

### How to customize the email

1. Open **Connect → Channels → Email Notifications** (same as above).
1. Open [**Connect → Channels**](https://grida.co/_/connect/channels)**Email Notifications** (same as above).
2. Configure the email fields:
- **Reply-To** (optional): where replies should go (e.g. `support@yourdomain.com`)
- **Subject**: the email subject template
Expand Down
1 change: 1 addition & 0 deletions docs/wg/platform/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Working group documents for Grida platform and infrastructure topics.
## Documents

- [Multi-tenant Custom Domains on Vercel](./multi-tenant-custom-domain-vercel)
- [Universal Docs Routing](./universal-docs-routing)
139 changes: 139 additions & 0 deletions docs/wg/platform/universal-docs-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: Universal Docs Routing (WG)
---

# Universal Docs Routing

## Summary

We need a **universal routing system** for user-facing docs and links where the
actual path depends on the user’s **org**, **project**, and sometimes the
**document context** (id + type). Docs should be written with a stable, concise
path like:

```
https://grida.co/_/connect/share
```

and resolved at runtime to the user-specific canonical path, for example:

```
https://grida.co/acme/project/00000000-0000-0000-0000-000000001234/connect/share
```

This keeps documentation readable while preserving correct routing for any
tenant and document.

---

## Terminology

- **Context path**: the org/project/doc portion of the URL.
- **Canonical path**: the fully expanded, tenant-specific path.
- **Universal route**: a shorthand path that starts with `/_/`.
- **Route registry**: the explicit list of shorthand routes and how they expand.

---

## Canonical path model

Canonical paths include the full tenant and document context:

- Project-level pages:
- `/:org/:project/dash`
- `/:org/:project/ciam`
- Document-level pages (example: forms):
- `/:org/:project/:docId/connect/share`

The doc type is **not** encoded in the universal path; it is resolved from
document context (id + type) at runtime.

---

## Universal path model

Universal routes replace the context path with a single reserved segment:

```
/_/<path>
```

Examples:

- `/_/dash` → `/:org/:project/dash`
- `/_/ciam` → `/:org/:project/ciam`
- `/_/connect/share` → `/:org/:project/:docId/connect/share`

`/_/` is **never canonical**. It is only an alias for documentation and
context-aware navigation.

---

## Resolution algorithm (runtime)

1. Detect the universal prefix (`/_/`).
2. Resolve **current context**:
- `org`, `project` from the active workspace/session.
- `docId` + `docType` from the active document (if required).
3. Match the remainder against the **route registry**.
4. Expand to the canonical path using the resolved context.
5. Route/redirect to the canonical path.

If a required context is missing, the router must halt with a context selection
flow (or a clear error) instead of guessing.

---

## Invariants

- `_` is a **reserved segment** and must never be used as an org or project id.
- Universal routes are **explicitly registered**, not inferred.
- Doc type must **not** be encoded into `/_/` paths (no `/forms/`, no query
params like `?doctype=forms`).
- Any universal path must resolve to **exactly one** canonical route.

---

## Uniqueness test (collision prevention)

Because routing is **not strictly rule-based**, shorthand names can collide.
We must enforce uniqueness with a looped test over the route registry.

**Requirement**

For every defined shorthand route, the matcher must return **exactly one**
result (itself).

**Sketch**

```
for (const route of universalRoutes) {
const matches = matchUniversalRoute(route.samplePath)
assert(matches.length === 1)
assert(matches[0].id === route.id)
}
```

Notes:

- Each route definition must include a `samplePath` that exercises its matcher.
- The test must run in CI to catch collisions early.

---

## Examples

| universal path | canonical path (example) |
| ----------------------- | ----------------------------------------------------------------------- |
| `/_/dash` | `/acme/project/dash` |
| `/_/ciam` | `/acme/project/ciam` |
| `/_/connect/share` | `/acme/project/00000000-0000-0000-0000-000000001234/connect/share` |

---

## Non-goals

- No implicit guessing or inference beyond the route registry.
- No alternate universal prefixes (`/_/_/`, `/__/`).
- No doc type leakage in the universal path.

7 changes: 7 additions & 0 deletions editor/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
# `editor`

## Universal routing (docs-friendly links)

Grida supports **universal routing** so documentation can link to stable, tenant-agnostic URLs like `https://grida.co/_/<path>` and have them resolved to canonical tenant/document routes at runtime.

- When you add a **new user-facing page** that should be referenced from docs, ensure it is registered in **universal routing**.
- When debugging docs links that point to the editor, start from the universal routing spec: `docs/wg/platform/universal-docs-routing.md`.
2 changes: 1 addition & 1 deletion editor/app/(api)/(public)/v1/west/t/invite/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { service_role } from "@/lib/supabase/server";
import { buildTenantSiteBaseUrl } from "@/lib/tenant-url";
import { buildTenantSiteBaseUrl } from "@/host/tenant-url";
import { headers } from "next/headers";
import { NextResponse, type NextRequest } from "next/server";
import { Platform } from "@/lib/platform";
Expand Down
2 changes: 1 addition & 1 deletion editor/app/(api)/(public)/v1/west/t/refresh/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { service_role } from "@/lib/supabase/server";
import { buildTenantSiteBaseUrl } from "@/lib/tenant-url";
import { buildTenantSiteBaseUrl } from "@/host/tenant-url";
import { headers } from "next/headers";
import { NextResponse, type NextRequest } from "next/server";
import { Platform } from "@/lib/platform";
Expand Down
Loading
Loading