Skip to content

Commit bb839c0

Browse files
committed
feat(new-deepnotes): user page list GET routes, prefs UIs, and group settings
Add GET /api/users/me/pages/recent and /favorites with session handlers and OpenAPI. Wire home (starting, recents, favorites, spatial default templates) and page editor (breadcrumb path, bump, favorite, remove recent). Group detail gains owner/admin join-request policy and soft-delete. Extend integration and worker 503 coverage; update TRPC_REST_MAP and PLAN_PROGRESS.
1 parent 3a1cabb commit bb839c0

21 files changed

Lines changed: 1188 additions & 68 deletions

new-deepnotes/PLAN_PROGRESS.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
| Area | State |
1414
|------|--------|
1515
| **API / REST** | TRPC_REST_MAP HTTP rows largely done; **collab WS** + **realtime** (legacy msgpackr) **not**. |
16-
| **SPA** | Auth, lists, **`PageEditorView`** (Tiptap+Y+collab REST), groups/**members**/invite/join, notifications list **with decrypt** (password-backed keyrings), **`/account`** (billing Stripe, password, email verify/change/confirm, 2FA, delete), theme (VueUse **`useColorMode`**). Missing: live collab WS, realtime, page/group prefs UIs beyond API, richer editor UX, native shells. |
16+
| **SPA** | Auth, lists, **`PageEditorView`** (Tiptap+Y+collab REST, path breadcrumb, bump/favorite/recent actions), groups/**members**/invite/join + **group settings** (join policy, soft-delete), notifications list **with decrypt**, **`/account`**, home **recents/favorites/starting/defaults** UIs, theme. Missing: live collab WS, realtime, richer editor UX, native shells. |
1717

1818
**Deferred (confirm vs parity):** optional anon `GET …/groups/:id/pages`; richer Stripe webhook tests. **Infra naming:** Workers/DO replaces standalone collab/realtime/scheduler processes.
1919

@@ -36,14 +36,14 @@
3636

3737
**Required for parity** unless *Deferred* above.
3838

39-
**Done:** typed client, `useSession`, register/login/demo/logout+2FA, home+pages, `PageEditorView`, groups+members+invite/join+crypto, notifications list + decrypt, **`AccountView`** (Stripe billing, password/email/2FA/delete), MSW+Vitest session matrix, ESLint restricted imports, Playwright **demo**, `apps/marketing`, theme (VueUse + header switcher).
39+
**Done:** typed client, `useSession`, register/login/demo/logout+2FA, home+pages (starting/recent/favorites/defaults), `PageEditorView` (path, bump, favorite, recent), groups+members+invite/join+crypto + **group settings** (join requests, soft-delete), notifications list + decrypt, **`AccountView`**, MSW+Vitest session matrix, ESLint restricted imports, Playwright **demo**, `apps/marketing`, theme (VueUse + header switcher).
4040

4141
**Open:**
4242

4343
- [ ] Password (registered) Playwright + stronger session/crypto asserts
4444
- [ ] Live editing: **collab WS** client + Phase 3 server path
4545
- [x] `[parity]` Account / billing / 2FA / email / delete — UIs wired to REST ([TRPC_REST_MAP](./docs/TRPC_REST_MAP.md))
46-
- [ ] `[parity]` Page ops + group settings + prefs (recents/favorites/path…) — UIs
46+
- [ ] `[parity]` Page ops + group settings **public/private crypto**, move/purge UI, snapshots UI
4747
- [ ] `[parity]` Editor UX vs legacy (rich + spatial/world if in scope)
4848
- [x] `[parity]` Notifications: decrypt/display as legacy
4949
- [ ] Legacy **realtime** equivalent (after protocol choice)
@@ -76,14 +76,15 @@
7676
- [ ] Stripe / high-risk: deeper tests when secrets allow
7777
- [ ] Staging CF: Hyperdrive + Postgres + Redis + WS topology load-tested
7878
- [ ] E2E beyond demo (password path)
79-
- [ ] **UI parity** in `@deepnotes/web` (account + notification decrypt done; page prefs, collab, realtime, editor still open — goal above)
79+
- [ ] **UI parity** in `@deepnotes/web` (account + notification decrypt + page prefs/home/editor + group join/delete done; **group public/private**, collab WS, realtime, deeper editor still open — goal above)
8080

8181
---
8282

8383
## Log (recent)
8484

8585
| Date | Note |
8686
|------|------|
87+
| 2026-04-29 | **Page prefs + group settings UI:** `GET …/pages/recent|favorites`, home (starting/recent/favorites/spatial defaults), editor path + bump/favorite/recent; group join-policy + soft-delete. |
8788
| 2026-04-29 | **`AccountView`** (`/account`): Stripe checkout/portal, password + email verify/change/confirm + raw keyrings, 2FA (enable/load/recovery/disable/devices), delete account; **`extractRawUserKeyringsBase64FromSession`** + **`build-password-and-email-confirm`**; notifications **msgpack decrypt** (`UserNotificationContent`); header link. |
8889
| 2026-04-29 | Theme: **`@vueuse/core` `useColorMode`**, hydrate pre-mount, **`ThemeSwitcher`**, Vitest hydration/migration tests, `App` unified shell (`data-testid="app-shell"`). |
8990
| 2026-04-29 | Compacted doc; parity goal + checklist preserved; condensed tests/E2E. |

new-deepnotes/apps/api-worker/src/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ describe("api-worker", () => {
3333
"GET",
3434
"/api/users/me/pages/path?initialPageId=aaaaaaaaaaaaaaaaaaaaa",
3535
],
36+
["GET", "/api/users/me/pages/recent"],
37+
["GET", "/api/users/me/pages/favorites"],
3638
["POST", "/api/users/me/pages/recent/remove"],
3739
["POST", "/api/users/me/pages/recent/clear"],
3840
["POST", "/api/users/me/pages/favorites"],

new-deepnotes/apps/api-worker/src/index.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,80 @@ app.get("/api/users/me/pages/path", async (c) => {
775775
}
776776
});
777777

778+
app.get("/api/users/me/pages/recent", async (c) => {
779+
const sessionEnv = getSessionEnv(c.env);
780+
if (sessionEnv == null) {
781+
return c.json(serviceUnavailableBody, 503);
782+
}
783+
const hyper = c.env.HYPERDRIVE;
784+
if (hyper == null) {
785+
return c.json(
786+
{
787+
code: "SERVICE_UNAVAILABLE" as const,
788+
message: "HYPERDRIVE binding is not configured.",
789+
},
790+
503,
791+
);
792+
}
793+
const db = getDbForConnectionString(hyper.connectionString);
794+
const cookieHeader = c.req.header("Cookie");
795+
try {
796+
const { performGetRecentPageIds } = await import("@deepnotes/session");
797+
const out = await performGetRecentPageIds({
798+
db,
799+
env: sessionEnv,
800+
accessCookie: readCookieHeader(cookieHeader, "accessToken"),
801+
});
802+
return c.json(out, 200);
803+
} catch (e) {
804+
const { SessionError } = await import("@deepnotes/session");
805+
if (e instanceof SessionError) {
806+
return c.json(
807+
{ code: e.code, message: e.message },
808+
e.status as ContentfulStatusCode,
809+
);
810+
}
811+
throw e;
812+
}
813+
});
814+
815+
app.get("/api/users/me/pages/favorites", async (c) => {
816+
const sessionEnv = getSessionEnv(c.env);
817+
if (sessionEnv == null) {
818+
return c.json(serviceUnavailableBody, 503);
819+
}
820+
const hyper = c.env.HYPERDRIVE;
821+
if (hyper == null) {
822+
return c.json(
823+
{
824+
code: "SERVICE_UNAVAILABLE" as const,
825+
message: "HYPERDRIVE binding is not configured.",
826+
},
827+
503,
828+
);
829+
}
830+
const db = getDbForConnectionString(hyper.connectionString);
831+
const cookieHeader = c.req.header("Cookie");
832+
try {
833+
const { performGetFavoritePageIds } = await import("@deepnotes/session");
834+
const out = await performGetFavoritePageIds({
835+
db,
836+
env: sessionEnv,
837+
accessCookie: readCookieHeader(cookieHeader, "accessToken"),
838+
});
839+
return c.json(out, 200);
840+
} catch (e) {
841+
const { SessionError } = await import("@deepnotes/session");
842+
if (e instanceof SessionError) {
843+
return c.json(
844+
{ code: e.code, message: e.message },
845+
e.status as ContentfulStatusCode,
846+
);
847+
}
848+
throw e;
849+
}
850+
});
851+
778852
app.post("/api/users/me/pages/recent/remove", async (c) => {
779853
const sessionEnv = getSessionEnv(c.env);
780854
if (sessionEnv == null) {

0 commit comments

Comments
 (0)