diff --git a/README.md b/README.md
index 7c01942..bcee756 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,16 @@
-# notify.atmo.tools
+# atmo.pub
Notifications for the AT Protocol. Any atproto app can ask a user for permission
to notify them; the user approves which apps may notify them in a web dashboard,
and deliveries go out (v1) through a Telegram bot.
-- **Dashboard** — sign in, approve apps, link Telegram: https://notify.atmo.tools
-- **Developer docs**: https://notify.atmo.tools/docs
-- **Live example sender**: https://example.notify.atmo.tools · [source](apps/example-sender)
-- **Relay API + `did:web`**: https://notifs.atmo.tools
+- **Dashboard** — sign in, approve apps, link Telegram: https://atmo.pub
+- **Developer docs**: https://atmo.pub/docs
+- **Live example sender**: https://example.atmo.pub · [source](apps/example-sender)
+- **Relay API + `did:web`**: https://relay.atmo.pub
-> `notify.atmo.tools` is the dashboard; `notifs.atmo.tools` (with an "s") is the
-> relay XRPC API. User-facing links point at `notify.atmo.tools`.
+> `atmo.pub` is the dashboard; `relay.atmo.pub` (with an "s") is the
+> relay XRPC API. User-facing links point at `atmo.pub`.
## Sending notifications from your app
@@ -21,19 +21,19 @@ Two endpoints, two auth mechanisms:
- **`send`** — authenticated by **your app's own DID key**. Delivers to a user who
has granted you permission.
-Full walkthrough with code is at **https://notify.atmo.tools/docs**, and
+Full walkthrough with code is at **https://atmo.pub/docs**, and
[`apps/example-sender`](apps/example-sender) is a complete, ~300-line reference
-implementation of both flows (live at https://example.notify.atmo.tools).
+implementation of both flows (live at https://example.atmo.pub).
## This repo
-- **`apps/relay`** — the Cloudflare Worker at `notifs.atmo.tools`. Pure XRPC: it
+- **`apps/relay`** — the Cloudflare Worker at `relay.atmo.pub`. Pure XRPC: it
verifies inbound service-auth JWTs, stores grants / pending requests / channels
in D1, rate-limits with KV, and dispatches deliveries through a Cloudflare Queue.
No firehose, no repo records, no signing.
-- **`apps/web`** — the SvelteKit dashboard at `notify.atmo.tools` (+ `/docs`).
-- **`apps/example-sender`** — one-page sender demo at `example.notify.atmo.tools`.
-- **`packages/lexicons`** — the shared `tools.atmo.notifs.*` lexicons (13) and
+- **`apps/web`** — the SvelteKit dashboard at `atmo.pub` (+ `/docs`).
+- **`apps/example-sender`** — one-page sender demo at `example.atmo.pub`.
+- **`packages/lexicons`** — the shared `pub.atmo.notify.*` lexicons (13) and
generated types.
Running, configuring, and deploying everything: **[DEVELOPMENT.md](DEVELOPMENT.md)**.
diff --git a/REDESIGN.md b/REDESIGN.md
new file mode 100644
index 0000000..42a1514
--- /dev/null
+++ b/REDESIGN.md
@@ -0,0 +1,108 @@
+# atmo.pub redesign — implementation plan
+
+Redesign of the webapp (`apps/web`) into a v2-style installable **PWA** with
+**web push**, an **inbox**, and **per-category routing**. Loose inspiration from
+`ref/design/project/v2.*` (read `v2.html` → its imports for screen-level detail).
+Backend work lands in the relay (`apps/relay`).
+
+## Locked decisions (2026-05-23)
+
+1. **Domains unchanged.** Relay stays `relay.atmo.pub` (`did:web` untouched),
+ webapp stays `atmo.pub`. This is a **UI rebrand only** → product name
+ becomes **atmo.pub**. No OAuth `ORIGIN`/client_id change.
+2. **Categories = free-form.** `send` carries an optional `category` string +
+ optional `categoryDescription`. No sender "declare" step; the relay learns
+ categories as they arrive. Routing keys on `(recipient, sender, category)`.
+3. **No email channel.** Channels are **push + telegram** only.
+4. **Trusted apps, hardcoded.** Replaces v2's "follows"/privacy/auto-allow: a
+ hardcoded list of sender DIDs in relay config that **auto-grant** at
+ `requestPermission` (skip pending). No Bluesky graph lookups, no privacy modes.
+5. **Inbox keeps everything.** Every delivered notification is stored in the
+ inbox (full history); per-category routing only decides which *alert* channels
+ also fire (push / telegram). No pruning for now (revisit retention later).
+6. **Keep the bell logo.** Wordmark renders `atmo.pub` (`.pub` muted), bell in accent.
+
+Reused as-is: the v2 color tokens are identical to `apps/web/src/routes/layout.css`,
+so theming carries straight over.
+
+## Routing model
+
+Routes available (after cutting email): `push`, `telegram`, `push+telegram`,
+`inbox` (inbox-only, no alert), `off` (alias of inbox-only for now), `default`
+(inherit user-wide `defaultRoute`). Resolution per notification:
+
+1. Always insert a row into `notifications` (inbox = canonical log).
+2. Look up routing for `(recipient, sender, category)`; fall back to the user's
+ `defaultRoute`.
+3. Fan out alerts to the resolved channels (push and/or telegram).
+
+## Phases
+
+### Phase 0 — Rebrand + foundation (no behavior change)
+- Rename product → **atmo.pub** in `apps/web`, `apps/homepage`, relay Telegram
+ copy (`telegram/commands.ts`, dashboard links), and docs. Wordmark `atmo.pub`,
+ keep bell `Logomark`.
+- Build a shared component layer in `apps/web/src/lib/components` ported from v2:
+ `Icon` (glyph set), `AppMark`, `RouteChip`, `IOSToggle`, `ActorStack`, plus the
+ Inbox/Apps/Settings shell (desktop split + mobile bottom-tab nav).
+- Remove the migrated landing (`/`) and `/docs` from `apps/web` (now in
+ `apps/homepage`); `/` becomes the clean Login.
+
+### Phase 1 — PWA shell + restyle current features (frontend only)
+- **Login** (clean), **Apps** (today's grants + pending + denied, v2 styling),
+ **Settings** (Telegram channel + existing toggle). **Inbox** tab present as a
+ "coming soon" placeholder. Responsive desktop + mobile.
+- Make `apps/web` an **installable PWA**: `manifest.webmanifest`, icons, minimal
+ service worker (install/offline shell only — no push yet).
+- Backend: none (rides existing `RelayRpc` binding).
+
+### Phase 2 — Web push (headline feature)
+- Relay:
+ - Generate **VAPID** keypair (public in config, private as `VAPID_PRIVATE_KEY`
+ secret).
+ - New `push_subscriptions` table: `(did, endpoint PK, p256dh, auth, created_at)`
+ — multiple per user. Telegram stays in `channels`. Migration `0004_push`.
+ - Web Push delivery (aes128gcm encryption + VAPID JWT via WebCrypto, no node) in
+ the dispatcher; dead endpoints (404/410) pruned.
+ - Binding methods `registerWebPush(did, subscription)` / `unregisterWebPush(did, endpoint)`.
+ - Until Phase 4, `send` fans out globally to push + telegram.
+- Client: service worker `push` → `showNotification`; `PushManager.subscribe` with
+ the VAPID public key; subscription registered via web server → binding. Settings
+ shows push devices (count + revoke).
+
+### Phase 3 — Inbox
+- Relay:
+ - `notifications` table: `(id, recipient_did, sender_did, category, title, body,
+ uri, actors_json, created_at, read_at)`. Migration `0005_inbox`.
+ - `send` writes a notification row for every delivery; gains optional
+ **`category`**, **`categoryDescription`**, **`actors[]`** (additive lexicon change).
+ - Binding methods `listNotifications(did, cursor)` (paged) + `markRead(did, ids|all)`.
+- Client: **Inbox** screen — actor stacks, route chips, unread state, mark-read;
+ swipe/grouped variants optional.
+- Update `apps/example-sender` + homepage `/docs` for the new `send` fields.
+
+### Phase 4 — Per-category routing + trusted apps
+- Relay:
+ - `app_categories` table: discovered `(sender_did, category, description, count,
+ last_seen)` so the routing UI can list them.
+ - `routing` table: `(recipient_did, sender_did, category, routes_json)`; user
+ `default_route` + optional `quiet_hours` columns on `users`. Migration `0006_routing`.
+ - Dispatcher resolves route per notification (see Routing model) and fans out.
+ - **Trusted apps**: hardcoded `TRUSTED_SENDERS` in relay config; `requestPermission`
+ auto-grants for those DIDs.
+ - (Optional) quiet hours applied at delivery; can be cut.
+ - Binding methods `getRouting(did)` / `setRouting(did, sender, category, routes)`;
+ extend `updateSettings` for `defaultRoute` (+ quiet hours).
+- Client: app-detail per-category routing UI, Default-routing screen, Channels,
+ read-only Trusted-apps list. (v2 has 3 routing-UI variations in
+ `v2-variations.jsx` — pick one.)
+
+## Deferred / out of scope (for now)
+- Email channel. Bluesky-graph "follows" privacy. Notification retention/pruning.
+- Native push (project is PWA-only — see `mobile-and-push` memory).
+
+## Cross-cutting
+- Theming reuse (tokens already match v2); accessibility (focus rings, reduced
+ motion already in `layout.css`); keep the `RelayRpc` service-binding boundary —
+ all new first-party methods go on the binding, never public XRPC. Only `send`
+ changes are public (and additive).
diff --git a/apps/example-sender/README.md b/apps/example-sender/README.md
index 311d939..5f58e0b 100644
--- a/apps/example-sender/README.md
+++ b/apps/example-sender/README.md
@@ -1,7 +1,7 @@
# Atmo Notifs — example sender
A tiny reference app showing how any AT Protocol app sends notifications through
-[Atmo Notifs](https://notifs.atmo.tools). One page, two auth mechanisms.
+[Atmo Notifs](https://relay.atmo.pub). One page, two auth mechanisms.
## The two auth mechanisms
diff --git a/apps/example-sender/src/lib/config.ts b/apps/example-sender/src/lib/config.ts
index 219e424..fae4c34 100644
--- a/apps/example-sender/src/lib/config.ts
+++ b/apps/example-sender/src/lib/config.ts
@@ -2,33 +2,33 @@
import { dev } from '$app/environment';
/** This app's public domain. */
-export const APP_DOMAIN = 'example.notify.atmo.tools';
+export const APP_DOMAIN = 'example.atmo.pub';
/** This app's sender DID — what the user approves and what `send` authenticates as. */
export const SENDER_DID = `did:web:${APP_DOMAIN}`;
/** Display name + description shown to the user when requesting permission. */
export const APP_TITLE = 'Example Sender';
-export const APP_DESCRIPTION = 'Demo of how to integrate with notify.atmo.tools.';
+export const APP_DESCRIPTION = 'Demo of how to integrate with atmo.pub.';
/**
* The relay's XRPC API. In dev (`vite dev`) talk to the local relay running via
* `wrangler dev`; in production use the deployed relay. The DID (service-auth
* `aud`) is the same either way — the local relay's `RELAY_DID` var matches.
*/
-export const RELAY_ORIGIN = dev ? 'http://localhost:8787' : 'https://notifs.atmo.tools';
-export const RELAY_DID = 'did:web:notifs.atmo.tools';
+export const RELAY_ORIGIN = dev ? 'http://localhost:8787' : 'https://relay.atmo.pub';
+export const RELAY_DID = 'did:web:relay.atmo.pub';
/** The web dashboard where users approve apps + link Telegram (note: no "s"). */
-export const DASHBOARD_ORIGIN = 'https://notify.atmo.tools';
+export const DASHBOARD_ORIGIN = 'https://atmo.pub';
/**
* OAuth scope: the sender app only needs `requestPermission` via the user's
* session (`send` uses this app's own DID key, not the user's OAuth). Requested
* as an individual `rpc` scope.
*/
-export const OAUTH_SCOPE = 'atproto rpc?lxm=tools.atmo.notifs.requestPermission&aud=*';
+export const OAUTH_SCOPE = 'atproto rpc?lxm=pub.atmo.notify.requestPermission&aud=*';
/** Branding. */
-export const PROJECT_NAME = 'notify.atmo.tools · example sender';
+export const PROJECT_NAME = 'atmo.pub · example sender';
export const GITHUB_URL = 'https://github.com/flo-bit/atproto-notify';
diff --git a/apps/example-sender/src/lib/server/relay.ts b/apps/example-sender/src/lib/server/relay.ts
index 7afc7b2..2076b6e 100644
--- a/apps/example-sender/src/lib/server/relay.ts
+++ b/apps/example-sender/src/lib/server/relay.ts
@@ -41,7 +41,7 @@ async function postRelay(jwt: string, lxm: string, body: object): Promise
): Promise<{ id: string; status: 'pending' | 'alreadyGranted' }> {
- const lxm = 'tools.atmo.notifs.requestPermission';
+ const lxm = 'pub.atmo.notify.requestPermission';
const auth = await client.get('com.atproto.server.getServiceAuth', {
params: { aud: RELAY_DID as Did, lxm: lxm as Nsid }
});
@@ -65,7 +65,7 @@ export async function sendAsSender(input: {
body: string;
uri?: string;
}): Promise<{ id: string; delivered: number }> {
- const lxm = 'tools.atmo.notifs.send';
+ const lxm = 'pub.atmo.notify.send';
const jwt = await mintSenderJwt(lxm);
return postRelay(jwt, lxm, input) as Promise<{ id: string; delivered: number }>;
}
diff --git a/apps/example-sender/src/routes/+page.svelte b/apps/example-sender/src/routes/+page.svelte
index 8399597..b8fcae0 100644
--- a/apps/example-sender/src/routes/+page.svelte
+++ b/apps/example-sender/src/routes/+page.svelte
@@ -93,7 +93,7 @@
{PROJECT_NAME}
A demo app showing how any AT Protocol app can send notifications through
- notify.atmo.tools. The code is open source — it wires up both auth mechanisms (user-OAuth
+ atmo.pub. The code is open source — it wires up both auth mechanisms (user-OAuth
for requesting permission, sender-DID for sending) in ~300 lines.
@@ -154,7 +154,7 @@
class="text-accent hover:underline"
href={DASHBOARD_ORIGIN}
target="_blank"
- rel="noreferrer">notify.atmo.toolsatmo.pub, where you can also connect Telegram. Already approved? Skip to Step 2.
{/if}
@@ -186,14 +186,14 @@
⚠ Sent, but you have no delivery channels linked. Connect Telegram at
notify.atmo.toolsatmo.pub.
{:else}
- atproto rpc?lxm=tools.atmo.notifs.requestPermission&aud=*
+ atproto rpc?lxm=pub.atmo.notify.requestPermission&aud=*
Then mint a service-auth JWT on the user's PDS via
diff --git a/apps/relay/src/env.ts b/apps/relay/src/env.ts
index 95f35e2..aa7f5a9 100644
--- a/apps/relay/src/env.ts
+++ b/apps/relay/src/env.ts
@@ -51,7 +51,7 @@ export interface Env {
/** Queue producer for async delivery. */
DISPATCH_QUEUE: Queue;
- /** `did:web:notifs.atmo.tools` — this relay's DID (var). */
+ /** `did:web:relay.atmo.pub` — this relay's DID (var). */
RELAY_DID: string;
/** Telegram bot username, used to build deep links (var). */
BOT_USERNAME: string;
diff --git a/apps/relay/src/lib/trusted.ts b/apps/relay/src/lib/trusted.ts
index c9a4998..0f0f99e 100644
--- a/apps/relay/src/lib/trusted.ts
+++ b/apps/relay/src/lib/trusted.ts
@@ -3,7 +3,7 @@ import type { Did } from '@atcute/lexicons';
// "Trusted apps": sender DIDs that are auto-granted at `requestPermission`
// (skipping the pending step). Hardcoded for now — add known-good app DIDs here.
export const TRUSTED_SENDERS: readonly Did[] = [
- // 'did:web:example.notify.atmo.tools',
+ // 'did:web:example.atmo.pub',
];
export function isTrustedSender(did: Did): boolean {
diff --git a/apps/relay/src/router.ts b/apps/relay/src/router.ts
index 654f787..b19da85 100644
--- a/apps/relay/src/router.ts
+++ b/apps/relay/src/router.ts
@@ -1,4 +1,4 @@
-import { ToolsAtmoNotifsRequestPermission, ToolsAtmoNotifsSend } from '@atmo/notifs-lexicons';
+import { PubAtmoNotifyRequestPermission, PubAtmoNotifySend } from '@atmo/notifs-lexicons';
import { XRPCRouter } from '@atcute/xrpc-server';
import { getVerifier } from './auth/verifier';
@@ -30,8 +30,8 @@ export function buildRouter(env: Env, ctx: ExecutionContext): XRPCRouter {
},
});
- router.addProcedure(ToolsAtmoNotifsRequestPermission.mainSchema, makeRequestPermission(app));
- router.addProcedure(ToolsAtmoNotifsSend.mainSchema, makeSend(app));
+ router.addProcedure(PubAtmoNotifyRequestPermission.mainSchema, makeRequestPermission(app));
+ router.addProcedure(PubAtmoNotifySend.mainSchema, makeSend(app));
return router;
}
diff --git a/apps/relay/src/rpc/entrypoint.ts b/apps/relay/src/rpc/entrypoint.ts
index b5dee3b..697b95e 100644
--- a/apps/relay/src/rpc/entrypoint.ts
+++ b/apps/relay/src/rpc/entrypoint.ts
@@ -11,17 +11,17 @@ import type {
NotifsRpc,
PushSubscriptionInput,
RoutingConfig,
- ToolsAtmoNotifsDenyPending,
- ToolsAtmoNotifsGetSettings,
- ToolsAtmoNotifsGrant,
- ToolsAtmoNotifsLinkChannel,
- ToolsAtmoNotifsListChannels,
- ToolsAtmoNotifsListGrants,
- ToolsAtmoNotifsListPending,
- ToolsAtmoNotifsMuteGrant,
- ToolsAtmoNotifsRevoke,
- ToolsAtmoNotifsUnlinkChannel,
- ToolsAtmoNotifsUpdateSettings,
+ PubAtmoNotifyDenyPending,
+ PubAtmoNotifyGetSettings,
+ PubAtmoNotifyGrant,
+ PubAtmoNotifyLinkChannel,
+ PubAtmoNotifyListChannels,
+ PubAtmoNotifyListGrants,
+ PubAtmoNotifyListPending,
+ PubAtmoNotifyMuteGrant,
+ PubAtmoNotifyRevoke,
+ PubAtmoNotifyUnlinkChannel,
+ PubAtmoNotifyUpdateSettings,
} from '@atmo/notifs-lexicons';
import type { Env } from '../env';
@@ -38,37 +38,37 @@ import * as ops from './ops';
* the shared contract the web app types its binding against.
*/
export class RelayRpc extends WorkerEntrypoint implements NotifsRpc {
- grant(did: Did, input: ToolsAtmoNotifsGrant.$input) {
+ grant(did: Did, input: PubAtmoNotifyGrant.$input) {
return ops.grant(this.env, did, input);
}
- revoke(did: Did, input: ToolsAtmoNotifsRevoke.$input) {
+ revoke(did: Did, input: PubAtmoNotifyRevoke.$input) {
return ops.revoke(this.env, did, input);
}
- denyPending(did: Did, input: ToolsAtmoNotifsDenyPending.$input) {
+ denyPending(did: Did, input: PubAtmoNotifyDenyPending.$input) {
return ops.denyPending(this.env, did, input);
}
- muteGrant(did: Did, input: ToolsAtmoNotifsMuteGrant.$input) {
+ muteGrant(did: Did, input: PubAtmoNotifyMuteGrant.$input) {
return ops.muteGrant(this.env, did, input);
}
- linkChannel(did: Did, input: ToolsAtmoNotifsLinkChannel.$input) {
+ linkChannel(did: Did, input: PubAtmoNotifyLinkChannel.$input) {
return ops.linkChannel(this.env, did, input);
}
- unlinkChannel(did: Did, input: ToolsAtmoNotifsUnlinkChannel.$input) {
+ unlinkChannel(did: Did, input: PubAtmoNotifyUnlinkChannel.$input) {
return ops.unlinkChannel(this.env, did, input);
}
- updateSettings(did: Did, input: ToolsAtmoNotifsUpdateSettings.$input) {
+ updateSettings(did: Did, input: PubAtmoNotifyUpdateSettings.$input) {
return ops.updateSettings(this.env, did, input);
}
- listGrants(did: Did): Promise {
+ listGrants(did: Did): Promise {
return ops.listGrants(this.env, did);
}
- listPending(did: Did): Promise {
+ listPending(did: Did): Promise {
return ops.listPending(this.env, did);
}
- listChannels(did: Did): Promise {
+ listChannels(did: Did): Promise {
return ops.listChannels(this.env, did);
}
- getSettings(did: Did): Promise {
+ getSettings(did: Did): Promise {
return ops.getSettings(this.env, did);
}
registerWebPush(did: Did, sub: PushSubscriptionInput) {
diff --git a/apps/relay/src/rpc/ops.ts b/apps/relay/src/rpc/ops.ts
index 566f229..6c697bf 100644
--- a/apps/relay/src/rpc/ops.ts
+++ b/apps/relay/src/rpc/ops.ts
@@ -16,17 +16,17 @@ import type {
PushSubscriptionInput,
RoutingApp,
RoutingConfig,
- ToolsAtmoNotifsDenyPending,
- ToolsAtmoNotifsGetSettings,
- ToolsAtmoNotifsGrant,
- ToolsAtmoNotifsLinkChannel,
- ToolsAtmoNotifsListChannels,
- ToolsAtmoNotifsListGrants,
- ToolsAtmoNotifsListPending,
- ToolsAtmoNotifsMuteGrant,
- ToolsAtmoNotifsRevoke,
- ToolsAtmoNotifsUnlinkChannel,
- ToolsAtmoNotifsUpdateSettings,
+ PubAtmoNotifyDenyPending,
+ PubAtmoNotifyGetSettings,
+ PubAtmoNotifyGrant,
+ PubAtmoNotifyLinkChannel,
+ PubAtmoNotifyListChannels,
+ PubAtmoNotifyListGrants,
+ PubAtmoNotifyListPending,
+ PubAtmoNotifyMuteGrant,
+ PubAtmoNotifyRevoke,
+ PubAtmoNotifyUnlinkChannel,
+ PubAtmoNotifyUpdateSettings,
} from '@atmo/notifs-lexicons';
import * as q from '../db/queries';
@@ -37,8 +37,8 @@ import { addMinutes, now, toIsoDatetime } from '../lib/time';
export async function grant(
env: Env,
did: Did,
- input: ToolsAtmoNotifsGrant.$input,
-): Promise {
+ input: PubAtmoNotifyGrant.$input,
+): Promise {
await q.ensureUser(env.DB, did, now());
// When granting from a pending request, copy its display metadata onto the
@@ -67,8 +67,8 @@ export async function grant(
export async function revoke(
env: Env,
did: Did,
- input: ToolsAtmoNotifsRevoke.$input,
-): Promise {
+ input: PubAtmoNotifyRevoke.$input,
+): Promise {
const revoked = await q.deleteGrant(env.DB, did, input.sender);
// The pending request (if any) for this pair is now irrelevant.
await q.deletePendingByPair(env.DB, did, input.sender);
@@ -79,8 +79,8 @@ export async function revoke(
export async function denyPending(
env: Env,
did: Did,
- input: ToolsAtmoNotifsDenyPending.$input,
-): Promise {
+ input: PubAtmoNotifyDenyPending.$input,
+): Promise {
// Delete the pending request without granting. Does not blocklist the sender;
// they may request again once rate limits allow.
const denied = await q.deletePendingById(env.DB, input.requestId, did);
@@ -91,8 +91,8 @@ export async function denyPending(
export async function muteGrant(
env: Env,
did: Did,
- input: ToolsAtmoNotifsMuteGrant.$input,
-): Promise {
+ input: PubAtmoNotifyMuteGrant.$input,
+): Promise {
await q.setGrantMuted(env.DB, did, input.sender, input.muted);
return { muted: input.muted };
@@ -101,8 +101,8 @@ export async function muteGrant(
export async function linkChannel(
env: Env,
did: Did,
- input: ToolsAtmoNotifsLinkChannel.$input,
-): Promise {
+ input: PubAtmoNotifyLinkChannel.$input,
+): Promise {
await q.ensureUser(env.DB, did, now());
const token = newLinkToken();
await q.insertLinkToken(env.DB, {
@@ -119,8 +119,8 @@ export async function linkChannel(
export async function unlinkChannel(
env: Env,
did: Did,
- input: ToolsAtmoNotifsUnlinkChannel.$input,
-): Promise {
+ input: PubAtmoNotifyUnlinkChannel.$input,
+): Promise {
const unlinked = await q.deleteChannel(env.DB, did, input.platform);
return { unlinked };
@@ -129,8 +129,8 @@ export async function unlinkChannel(
export async function updateSettings(
env: Env,
did: Did,
- input: ToolsAtmoNotifsUpdateSettings.$input,
-): Promise {
+ input: PubAtmoNotifyUpdateSettings.$input,
+): Promise {
await q.ensureUser(env.DB, did, now());
// Partial PATCH: only touch fields present in the input.
@@ -150,7 +150,7 @@ export async function updateSettings(
export async function listGrants(
env: Env,
did: Did,
-): Promise {
+): Promise {
const rows = await q.listGrantsForRecipient(env.DB, did);
return {
@@ -172,7 +172,7 @@ export async function listGrants(
export async function listPending(
env: Env,
did: Did,
-): Promise {
+): Promise {
const rows = await q.listPendingForRecipient(env.DB, did, now());
return {
@@ -196,7 +196,7 @@ export async function listPending(
export async function listChannels(
env: Env,
did: Did,
-): Promise {
+): Promise {
const rows = await q.listChannelsForDid(env.DB, did);
return {
@@ -211,7 +211,7 @@ export async function listChannels(
export async function getSettings(
env: Env,
did: Did,
-): Promise {
+): Promise {
// Ensure the row exists so we return stored defaults rather than guessing.
await q.ensureUser(env.DB, did, now());
const user = await q.getUser(env.DB, did);
diff --git a/apps/relay/src/telegram/commands.ts b/apps/relay/src/telegram/commands.ts
index da8b56b..6c1acfa 100644
--- a/apps/relay/src/telegram/commands.ts
+++ b/apps/relay/src/telegram/commands.ts
@@ -12,8 +12,9 @@ import { now } from '../lib/time';
import type { TelegramMessage } from './webhook';
-// The dashboard is the web app (notify.atmo.tools), not the relay (notifs.atmo.tools).
-const DASHBOARD_URL = 'https://notify.atmo.tools/dashboard';
+// Telegram linking lives in the web app's Channels settings (atmo.pub), not the
+// relay (relay.atmo.pub). Deep-link straight to that tab.
+const DASHBOARD_URL = 'https://atmo.pub/settings?tab=channels';
const PLATFORM = 'telegram';
/** Dispatch a `/command` message to its handler. */
@@ -190,8 +191,7 @@ export function settingsKeyboard(enabled: boolean): InlineKeyboardMarkup {
};
}
-const NOT_LINKED =
- 'This Telegram account is not linked yet. Link it from https://notify.atmo.tools/dashboard';
+const NOT_LINKED = `This Telegram account is not linked yet. Link it from ${DASHBOARD_URL}`;
function replyText(env: Env, chatId: number, text: string): Promise<{ message_id: number }> {
return sendMessage(env, { chat_id: chatId, text });
diff --git a/apps/relay/src/well-known.ts b/apps/relay/src/well-known.ts
index 23c83aa..de29c4f 100644
--- a/apps/relay/src/well-known.ts
+++ b/apps/relay/src/well-known.ts
@@ -1,5 +1,5 @@
-import requestPermission from '@atmo/notifs-lexicons/lexicons/tools/atmo/notifs/requestPermission.json';
-import send from '@atmo/notifs-lexicons/lexicons/tools/atmo/notifs/send.json';
+import requestPermission from '@atmo/notifs-lexicons/lexicons/pub/atmo/notify/requestPermission.json';
+import send from '@atmo/notifs-lexicons/lexicons/pub/atmo/notify/send.json';
import type { Env } from './env';
@@ -8,8 +8,8 @@ import type { Env } from './env';
// so their lexicon JSON is intentionally NOT served (the files remain in the
// package purely as type-generation input). See src/rpc/entrypoint.ts.
const LEXICONS: Record = {
- 'tools.atmo.notifs.requestPermission': requestPermission,
- 'tools.atmo.notifs.send': send,
+ 'pub.atmo.notify.requestPermission': requestPermission,
+ 'pub.atmo.notify.send': send,
};
/**
diff --git a/apps/relay/src/xrpc/requestPermission.ts b/apps/relay/src/xrpc/requestPermission.ts
index fd29850..7dd13d4 100644
--- a/apps/relay/src/xrpc/requestPermission.ts
+++ b/apps/relay/src/xrpc/requestPermission.ts
@@ -1,4 +1,4 @@
-import { ToolsAtmoNotifsRequestPermission } from '@atmo/notifs-lexicons';
+import { PubAtmoNotifyRequestPermission } from '@atmo/notifs-lexicons';
import type { Did } from '@atcute/lexicons';
import { isDid } from '@atcute/lexicons/syntax';
import { InvalidRequestError, json, type ProcedureConfig } from '@atcute/xrpc-server';
@@ -13,7 +13,7 @@ import { isTrustedSender } from '../lib/trusted';
import { ensureSenderProfile } from '../profile/fetch';
import { checkAndIncrement } from '../ratelimit';
-const LXM = 'tools.atmo.notifs.requestPermission';
+const LXM = 'pub.atmo.notify.requestPermission';
// New pending-request caps, both per rolling hour.
const PER_RECIPIENT_LIMIT = 50; // NEW: stops one OAuth'd app spamming a user
@@ -22,7 +22,7 @@ const WINDOW_SECONDS = 60 * 60;
export function makeRequestPermission(
app: AppContext,
-): ProcedureConfig {
+): ProcedureConfig {
return {
handler: async ({ request, input }) => {
// Auth flipped to the user path: the JWT issuer is the recipient (the user
diff --git a/apps/relay/src/xrpc/send.ts b/apps/relay/src/xrpc/send.ts
index 367373e..9d1a9d2 100644
--- a/apps/relay/src/xrpc/send.ts
+++ b/apps/relay/src/xrpc/send.ts
@@ -1,4 +1,4 @@
-import { ToolsAtmoNotifsSend } from '@atmo/notifs-lexicons';
+import { PubAtmoNotifySend } from '@atmo/notifs-lexicons';
import type { Did } from '@atcute/lexicons';
import { json, type ProcedureConfig } from '@atcute/xrpc-server';
@@ -10,7 +10,7 @@ import { newId } from '../lib/ids';
import { now } from '../lib/time';
import { checkAndIncrement } from '../ratelimit';
-const LXM = 'tools.atmo.notifs.send';
+const LXM = 'pub.atmo.notify.send';
// Per-pair limits: at most 1 notification/second and 100/day.
const PER_SECOND_LIMIT = 1;
@@ -18,7 +18,7 @@ const PER_SECOND_WINDOW = 1;
const PER_DAY_LIMIT = 100;
const PER_DAY_WINDOW = 24 * 60 * 60;
-export function makeSend(app: AppContext): ProcedureConfig {
+export function makeSend(app: AppContext): ProcedureConfig {
return {
handler: async ({ request, input }) => {
const { senderDid } = await verifySenderRequest(app.verifier, request, LXM);
diff --git a/apps/relay/test/auth.test.ts b/apps/relay/test/auth.test.ts
index 1563a0a..1856348 100644
--- a/apps/relay/test/auth.test.ts
+++ b/apps/relay/test/auth.test.ts
@@ -24,7 +24,7 @@ beforeAll(() => {
// exercises the user-JWT verifier now that the management methods moved to the
// service binding. A valid body is sent in every case so the request reaches the
// auth check rather than tripping schema validation first.
-const REQ = 'tools.atmo.notifs.requestPermission';
+const REQ = 'pub.atmo.notify.requestPermission';
const SENDER: Did = 'did:plc:authsender';
const BODY = { senderDid: SENDER, title: 'Test' };
@@ -69,7 +69,7 @@ it('rejects a JWT scoped to a different lexicon method', async () => {
const user = await makeIdentity('did:plc:authlxm');
mockPlc(user);
// Token authorizes `send`, but we call `requestPermission`.
- const jwt = await makeJwt(user, { lxm: 'tools.atmo.notifs.send' });
+ const jwt = await makeJwt(user, { lxm: 'pub.atmo.notify.send' });
const res = await call(xrpcPost(REQ, jwt, BODY));
diff --git a/apps/relay/test/helpers.ts b/apps/relay/test/helpers.ts
index ed9c885..dc7b1d3 100644
--- a/apps/relay/test/helpers.ts
+++ b/apps/relay/test/helpers.ts
@@ -2,7 +2,7 @@ import { P256PrivateKeyExportable } from '@atcute/crypto';
import type { Did, Nsid } from '@atcute/lexicons';
import { createServiceJwt } from '@atcute/xrpc-server/auth';
-export const RELAY_DID = 'did:web:notifs.atmo.tools';
+export const RELAY_DID = 'did:web:relay.atmo.pub';
// ---------------------------------------------------------------------------
// Outbound fetch mocking
@@ -140,7 +140,7 @@ export function makeJwt(identity: TestIdentity, options: JwtOptions): Promise {
makeBskyProfileMock();
});
-const REQ = 'tools.atmo.notifs.requestPermission';
+const REQ = 'pub.atmo.notify.requestPermission';
// requestPermission is now user-authenticated; the sender is a plain DID in the body.
const SENDER: Did = 'did:plc:somesender';
diff --git a/apps/relay/test/send.test.ts b/apps/relay/test/send.test.ts
index b5e64f6..a70ce47 100644
--- a/apps/relay/test/send.test.ts
+++ b/apps/relay/test/send.test.ts
@@ -12,7 +12,7 @@ beforeAll(() => {
mockTelegramOk();
});
-const SEND = 'tools.atmo.notifs.send';
+const SEND = 'pub.atmo.notify.send';
const RECIPIENT: Did = 'did:plc:sendrecipient';
async function call(req: Request): Promise {
diff --git a/apps/relay/test/telegram-link.test.ts b/apps/relay/test/telegram-link.test.ts
index f948de6..5fd9e69 100644
--- a/apps/relay/test/telegram-link.test.ts
+++ b/apps/relay/test/telegram-link.test.ts
@@ -22,7 +22,7 @@ async function call(req: Request): Promise {
}
function webhook(update: unknown, secret = SECRET): Request {
- return new Request(`https://notifs.atmo.tools/telegram/webhook/${secret}`, {
+ return new Request(`https://relay.atmo.pub/telegram/webhook/${secret}`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(update),
diff --git a/apps/relay/wrangler.toml b/apps/relay/wrangler.toml
index 8ebaa7f..e76655d 100644
--- a/apps/relay/wrangler.toml
+++ b/apps/relay/wrangler.toml
@@ -5,7 +5,7 @@ compatibility_flags = ["nodejs_compat"]
# Custom domains bind the whole hostname (no path/wildcard); the Worker's own
# router dispatches paths internally.
-routes = [{ pattern = "notifs.atmo.tools", custom_domain = true }]
+routes = [{ pattern = "relay.atmo.pub", custom_domain = true }]
[[d1_databases]]
binding = "DB"
@@ -36,7 +36,7 @@ crons = ["0 3 * * *"]
enabled = true
[vars]
-RELAY_DID = "did:web:notifs.atmo.tools"
+RELAY_DID = "did:web:relay.atmo.pub"
BOT_USERNAME = "atmo_notify_bot" # e.g. "atmonotifsbot"
# Web push (VAPID). Generate with `pnpm vapid:keygen`. The public key is also the
# browser applicationServerKey — paste the SAME value into apps/web VAPID_PUBLIC_KEY.
diff --git a/apps/web/README.md b/apps/web/README.md
index d534896..1a665fd 100644
--- a/apps/web/README.md
+++ b/apps/web/README.md
@@ -52,7 +52,7 @@ pnpm dev
Set the env vars in `.env` (see `.env.example`): `ORIGIN`, `COOKIE_SECRET`,
`CLIENT_ASSERTION_KEY`. The relay must be reachable at the domain in
-`src/lib/config.ts` (`notifs.atmo.tools`) for dashboard data to load — point it at
+`src/lib/config.ts` (`relay.atmo.pub`) for dashboard data to load — point it at
a local relay during development if needed.
```sh
@@ -66,7 +66,7 @@ Uses `@sveltejs/adapter-cloudflare` (Workers + static assets). Run these from
`apps/web/` in a Cloudflare-authenticated terminal.
1. **Pick the domain.** In `wrangler.jsonc`, set `name` and `vars.ORIGIN`
- (e.g. `https://notifs-web.atmo.tools`). `ORIGIN` must equal the served URL —
+ (e.g. `https://atmo.pub`). `ORIGIN` must equal the served URL —
the atproto OAuth `client_id` is derived from it.
2. **Create the KV namespaces** and paste the ids into `wrangler.jsonc`:
```sh
@@ -87,4 +87,4 @@ Uses `@sveltejs/adapter-cloudflare` (Workers + static assets). Run these from
The OAuth client metadata is served dynamically by `@svelte-atproto/oauth` — no
static file to write. The dashboard needs the relay reachable at the domain in
-`src/lib/config.ts` (`notifs.atmo.tools`), so deploy the relay too.
+`src/lib/config.ts` (`relay.atmo.pub`), so deploy the relay too.
diff --git a/apps/web/src/lib/atproto/index.ts b/apps/web/src/lib/atproto/index.ts
index 26e84a4..15dcf2d 100644
--- a/apps/web/src/lib/atproto/index.ts
+++ b/apps/web/src/lib/atproto/index.ts
@@ -10,7 +10,7 @@ export const atproto = createAtprotoAuth({
origin: env.ORIGIN,
cookieSecret: env.COOKIE_SECRET,
clientAssertionKey: env.CLIENT_ASSERTION_KEY,
- // Requests the `tools.atmo.notifs.authUser` permission set scoped to the relay.
+ // Requests the `pub.atmo.notify.authUser` permission set scoped to the relay.
scope: OAUTH_SCOPE,
sessions: cloudflareKV('OAUTH_SESSIONS'),
states: cloudflareKV('OAUTH_STATES', { ttl: 600 })
diff --git a/apps/web/src/lib/server/relay.ts b/apps/web/src/lib/server/relay.ts
index 2021ddf..1fa8fde 100644
--- a/apps/web/src/lib/server/relay.ts
+++ b/apps/web/src/lib/server/relay.ts
@@ -15,13 +15,13 @@ import type {
CategoryRoute,
MarkReadInput,
PushSubscriptionInput,
- ToolsAtmoNotifsDenyPending,
- ToolsAtmoNotifsGrant,
- ToolsAtmoNotifsLinkChannel,
- ToolsAtmoNotifsMuteGrant,
- ToolsAtmoNotifsRevoke,
- ToolsAtmoNotifsUnlinkChannel,
- ToolsAtmoNotifsUpdateSettings
+ PubAtmoNotifyDenyPending,
+ PubAtmoNotifyGrant,
+ PubAtmoNotifyLinkChannel,
+ PubAtmoNotifyMuteGrant,
+ PubAtmoNotifyRevoke,
+ PubAtmoNotifyUnlinkChannel,
+ PubAtmoNotifyUpdateSettings
} from '@atmo/notifs-lexicons';
/**
@@ -45,13 +45,13 @@ export function relayFor(platform: App.Platform | undefined, did: Did | null) {
listPending: () => svc.listPending(did),
listChannels: () => svc.listChannels(did),
getSettings: () => svc.getSettings(did),
- grant: (input: ToolsAtmoNotifsGrant.$input) => svc.grant(did, input),
- revoke: (input: ToolsAtmoNotifsRevoke.$input) => svc.revoke(did, input),
- denyPending: (input: ToolsAtmoNotifsDenyPending.$input) => svc.denyPending(did, input),
- muteGrant: (input: ToolsAtmoNotifsMuteGrant.$input) => svc.muteGrant(did, input),
- linkChannel: (input: ToolsAtmoNotifsLinkChannel.$input) => svc.linkChannel(did, input),
- unlinkChannel: (input: ToolsAtmoNotifsUnlinkChannel.$input) => svc.unlinkChannel(did, input),
- updateSettings: (input: ToolsAtmoNotifsUpdateSettings.$input) =>
+ grant: (input: PubAtmoNotifyGrant.$input) => svc.grant(did, input),
+ revoke: (input: PubAtmoNotifyRevoke.$input) => svc.revoke(did, input),
+ denyPending: (input: PubAtmoNotifyDenyPending.$input) => svc.denyPending(did, input),
+ muteGrant: (input: PubAtmoNotifyMuteGrant.$input) => svc.muteGrant(did, input),
+ linkChannel: (input: PubAtmoNotifyLinkChannel.$input) => svc.linkChannel(did, input),
+ unlinkChannel: (input: PubAtmoNotifyUnlinkChannel.$input) => svc.unlinkChannel(did, input),
+ updateSettings: (input: PubAtmoNotifyUpdateSettings.$input) =>
svc.updateSettings(did, input),
registerWebPush: (sub: PushSubscriptionInput) => svc.registerWebPush(did, sub),
unregisterWebPush: (endpoint: string) => svc.unregisterWebPush(did, endpoint),
diff --git a/apps/web/wrangler.jsonc b/apps/web/wrangler.jsonc
index 7d35d83..cca7c79 100644
--- a/apps/web/wrangler.jsonc
+++ b/apps/web/wrangler.jsonc
@@ -12,7 +12,7 @@
// Public origin of the deployed app (NOT secret). Must equal the domain you
// serve from — the atproto OAuth client_id is derived from it.
"vars": {
- "ORIGIN": "https://notify.atmo.tools"
+ "ORIGIN": "https://atmo.pub"
},
// KV namespaces required by @svelte-atproto/oauth's `cloudflareKV` stores
// (see src/lib/atproto/index.ts). Create them and paste the ids:
@@ -46,5 +46,5 @@
// CLIENT_ASSERTION_KEY (atproto-oauth keygen | wrangler secret put CLIENT_ASSERTION_KEY)
//
// Custom domain: bind it in the CF dashboard, or add:
- // "routes": [{ "pattern": "notifs-web.atmo.tools", "custom_domain": true }]
+ // "routes": [{ "pattern": "atmo.pub", "custom_domain": true }]
}
\ No newline at end of file
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/denyPending.json b/packages/lexicons/lexicons/pub/atmo/notify/denyPending.json
similarity index 94%
rename from packages/lexicons/lexicons/tools/atmo/notifs/denyPending.json
rename to packages/lexicons/lexicons/pub/atmo/notify/denyPending.json
index eebda2a..0b4dac8 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/denyPending.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/denyPending.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.denyPending",
+ "id": "pub.atmo.notify.denyPending",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/getSettings.json b/packages/lexicons/lexicons/pub/atmo/notify/getSettings.json
similarity index 95%
rename from packages/lexicons/lexicons/tools/atmo/notifs/getSettings.json
rename to packages/lexicons/lexicons/pub/atmo/notify/getSettings.json
index ab7b595..2e67100 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/getSettings.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/getSettings.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.getSettings",
+ "id": "pub.atmo.notify.getSettings",
"defs": {
"main": {
"type": "query",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/grant.json b/packages/lexicons/lexicons/pub/atmo/notify/grant.json
similarity index 96%
rename from packages/lexicons/lexicons/tools/atmo/notifs/grant.json
rename to packages/lexicons/lexicons/pub/atmo/notify/grant.json
index ae8216d..19a5e78 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/grant.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/grant.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.grant",
+ "id": "pub.atmo.notify.grant",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/linkChannel.json b/packages/lexicons/lexicons/pub/atmo/notify/linkChannel.json
similarity index 95%
rename from packages/lexicons/lexicons/tools/atmo/notifs/linkChannel.json
rename to packages/lexicons/lexicons/pub/atmo/notify/linkChannel.json
index 4a976f1..d4807e7 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/linkChannel.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/linkChannel.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.linkChannel",
+ "id": "pub.atmo.notify.linkChannel",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/listChannels.json b/packages/lexicons/lexicons/pub/atmo/notify/listChannels.json
similarity index 94%
rename from packages/lexicons/lexicons/tools/atmo/notifs/listChannels.json
rename to packages/lexicons/lexicons/pub/atmo/notify/listChannels.json
index e6b7ee5..79d2b45 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/listChannels.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/listChannels.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.listChannels",
+ "id": "pub.atmo.notify.listChannels",
"defs": {
"main": {
"type": "query",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/listGrants.json b/packages/lexicons/lexicons/pub/atmo/notify/listGrants.json
similarity index 97%
rename from packages/lexicons/lexicons/tools/atmo/notifs/listGrants.json
rename to packages/lexicons/lexicons/pub/atmo/notify/listGrants.json
index 78b7543..a27d0f5 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/listGrants.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/listGrants.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.listGrants",
+ "id": "pub.atmo.notify.listGrants",
"defs": {
"main": {
"type": "query",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/listPending.json b/packages/lexicons/lexicons/pub/atmo/notify/listPending.json
similarity index 97%
rename from packages/lexicons/lexicons/tools/atmo/notifs/listPending.json
rename to packages/lexicons/lexicons/pub/atmo/notify/listPending.json
index 85b4efc..bd3933f 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/listPending.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/listPending.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.listPending",
+ "id": "pub.atmo.notify.listPending",
"defs": {
"main": {
"type": "query",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/muteGrant.json b/packages/lexicons/lexicons/pub/atmo/notify/muteGrant.json
similarity index 95%
rename from packages/lexicons/lexicons/tools/atmo/notifs/muteGrant.json
rename to packages/lexicons/lexicons/pub/atmo/notify/muteGrant.json
index dad0581..b8eec3f 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/muteGrant.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/muteGrant.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.muteGrant",
+ "id": "pub.atmo.notify.muteGrant",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/requestPermission.json b/packages/lexicons/lexicons/pub/atmo/notify/requestPermission.json
similarity index 97%
rename from packages/lexicons/lexicons/tools/atmo/notifs/requestPermission.json
rename to packages/lexicons/lexicons/pub/atmo/notify/requestPermission.json
index 10855e3..56d91bb 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/requestPermission.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/requestPermission.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.requestPermission",
+ "id": "pub.atmo.notify.requestPermission",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/revoke.json b/packages/lexicons/lexicons/pub/atmo/notify/revoke.json
similarity index 95%
rename from packages/lexicons/lexicons/tools/atmo/notifs/revoke.json
rename to packages/lexicons/lexicons/pub/atmo/notify/revoke.json
index bee6d51..665606f 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/revoke.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/revoke.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.revoke",
+ "id": "pub.atmo.notify.revoke",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/send.json b/packages/lexicons/lexicons/pub/atmo/notify/send.json
similarity index 98%
rename from packages/lexicons/lexicons/tools/atmo/notifs/send.json
rename to packages/lexicons/lexicons/pub/atmo/notify/send.json
index 8d61c8b..cf5fc67 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/send.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/send.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.send",
+ "id": "pub.atmo.notify.send",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/unlinkChannel.json b/packages/lexicons/lexicons/pub/atmo/notify/unlinkChannel.json
similarity index 94%
rename from packages/lexicons/lexicons/tools/atmo/notifs/unlinkChannel.json
rename to packages/lexicons/lexicons/pub/atmo/notify/unlinkChannel.json
index 7692a5e..74f93b1 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/unlinkChannel.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/unlinkChannel.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.unlinkChannel",
+ "id": "pub.atmo.notify.unlinkChannel",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/lexicons/tools/atmo/notifs/updateSettings.json b/packages/lexicons/lexicons/pub/atmo/notify/updateSettings.json
similarity index 95%
rename from packages/lexicons/lexicons/tools/atmo/notifs/updateSettings.json
rename to packages/lexicons/lexicons/pub/atmo/notify/updateSettings.json
index 1e07d8d..2c45c30 100644
--- a/packages/lexicons/lexicons/tools/atmo/notifs/updateSettings.json
+++ b/packages/lexicons/lexicons/pub/atmo/notify/updateSettings.json
@@ -1,6 +1,6 @@
{
"lexicon": 1,
- "id": "tools.atmo.notifs.updateSettings",
+ "id": "pub.atmo.notify.updateSettings",
"defs": {
"main": {
"type": "procedure",
diff --git a/packages/lexicons/src/index.ts b/packages/lexicons/src/index.ts
index 4dab56c..b0d4578 100644
--- a/packages/lexicons/src/index.ts
+++ b/packages/lexicons/src/index.ts
@@ -1,7 +1,7 @@
// Public entrypoint for the shared lexicons package.
//
// Re-exports the code generated by `@atcute/lex-cli` (run `pnpm generate`).
-// Each lexicon is exposed as a namespace, e.g. `ToolsAtmoNotifsSend`, carrying:
+// Each lexicon is exposed as a namespace, e.g. `PubAtmoNotifySend`, carrying:
// - `mainSchema` — the XRPC metadata you pass to `XRPCRouter.add*`
// - `$input` / `$output` / `$params` — inferred request/response types
// - view types for `ref`'d defs (e.g. `GrantView`)
diff --git a/packages/lexicons/src/rpc.ts b/packages/lexicons/src/rpc.ts
index 503744a..347a25b 100644
--- a/packages/lexicons/src/rpc.ts
+++ b/packages/lexicons/src/rpc.ts
@@ -14,17 +14,17 @@
import type { Did } from '@atcute/lexicons';
import type {
- ToolsAtmoNotifsDenyPending,
- ToolsAtmoNotifsGetSettings,
- ToolsAtmoNotifsGrant,
- ToolsAtmoNotifsLinkChannel,
- ToolsAtmoNotifsListChannels,
- ToolsAtmoNotifsListGrants,
- ToolsAtmoNotifsListPending,
- ToolsAtmoNotifsMuteGrant,
- ToolsAtmoNotifsRevoke,
- ToolsAtmoNotifsUnlinkChannel,
- ToolsAtmoNotifsUpdateSettings,
+ PubAtmoNotifyDenyPending,
+ PubAtmoNotifyGetSettings,
+ PubAtmoNotifyGrant,
+ PubAtmoNotifyLinkChannel,
+ PubAtmoNotifyListChannels,
+ PubAtmoNotifyListGrants,
+ PubAtmoNotifyListPending,
+ PubAtmoNotifyMuteGrant,
+ PubAtmoNotifyRevoke,
+ PubAtmoNotifyUnlinkChannel,
+ PubAtmoNotifyUpdateSettings,
} from './lexicons/index.js';
/** A browser PushSubscription, flattened (binding-only; no public lexicon). */
@@ -93,32 +93,32 @@ export interface RoutingConfig {
}
export interface NotifsRpc {
- grant(did: Did, input: ToolsAtmoNotifsGrant.$input): Promise;
- revoke(did: Did, input: ToolsAtmoNotifsRevoke.$input): Promise;
+ grant(did: Did, input: PubAtmoNotifyGrant.$input): Promise;
+ revoke(did: Did, input: PubAtmoNotifyRevoke.$input): Promise;
denyPending(
did: Did,
- input: ToolsAtmoNotifsDenyPending.$input,
- ): Promise;
+ input: PubAtmoNotifyDenyPending.$input,
+ ): Promise;
muteGrant(
did: Did,
- input: ToolsAtmoNotifsMuteGrant.$input,
- ): Promise;
+ input: PubAtmoNotifyMuteGrant.$input,
+ ): Promise;
linkChannel(
did: Did,
- input: ToolsAtmoNotifsLinkChannel.$input,
- ): Promise;
+ input: PubAtmoNotifyLinkChannel.$input,
+ ): Promise;
unlinkChannel(
did: Did,
- input: ToolsAtmoNotifsUnlinkChannel.$input,
- ): Promise;
+ input: PubAtmoNotifyUnlinkChannel.$input,
+ ): Promise;
updateSettings(
did: Did,
- input: ToolsAtmoNotifsUpdateSettings.$input,
- ): Promise;
- listGrants(did: Did): Promise;
- listPending(did: Did): Promise;
- listChannels(did: Did): Promise;
- getSettings(did: Did): Promise;
+ input: PubAtmoNotifyUpdateSettings.$input,
+ ): Promise;
+ listGrants(did: Did): Promise;
+ listPending(did: Did): Promise;
+ listChannels(did: Did): Promise;
+ getSettings(did: Did): Promise;
// Web push (binding-only; no public lexicon).
registerWebPush(did: Did, sub: PushSubscriptionInput): Promise<{ registered: boolean }>;