Skip to content

Commit f72da8c

Browse files
committed
feat(new-deepnotes): marketing vite-ssg site, Drizzle prod migration doc, and plan progress
Add apps/marketing static landing with vite-ssg/single-page and VITE_WEB_APP_URL for the app link. Document empty-to-current Drizzle migrations for staging/production. Extend DEPLOY_CLOUDFLARE for the marketing artifact.
1 parent 219c524 commit f72da8c

31 files changed

Lines changed: 1223 additions & 31 deletions

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- vue-shadcn and Tailwind for UI.
2+
- Feature-based folder structure.

new-deepnotes/PLAN_PROGRESS.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Checklist for [docs/RESTART_PLAN.md](../docs/RESTART_PLAN.md). **Procedure-level map:** [docs/TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md) (legacy tRPC + app-server WebSocket rows are marked **implemented** or **removed**).
44

5-
**Last reviewed:** 2026-04-28
5+
**Last reviewed:** 2026-04-29
66

77
---
88

@@ -14,7 +14,7 @@ Checklist for [docs/RESTART_PLAN.md](../docs/RESTART_PLAN.md). **Procedure-level
1414
| **1** — Legacy repo hygiene | **Optional** | Only if still editing the old monorepo. |
1515
| **2** — Repo bootstrap | **Done** | Turbo, CI, template DB tests, deploy notes. |
1616
| **3** — REST + Drizzle | **In progress** | All **HTTP** items in TRPC_REST_MAP through **slice 10** (`…/collab-updates`), **Stripe**, **2FA**, membership, crypto bootstrap routes. **Not started:** **live Yjs collab WebSocket** and **realtime** (legacy `realtime-server` / msgpackr — separate from collab). |
17-
| **4** — Client MVP | **In progress** | Auth, register (E2EE), pages list, Tiptap+Yjs+REST collab, groups + invites/join E2EE, notifications (list/read; **no** body decrypt), MSW contract smoke, ESLint import walls. **Done:** Playwright smoke for **demo** session (httpOnly tokens, `loggedIn` hint, reload → bootstrap/refresh). **Next:** password-login E2E (registered user fixture or flow); optional demo keyring assertions; marketing `vite-ssg` per plan. |
17+
| **4** — Client MVP | **In progress** | Auth, register (E2EE), pages list, Tiptap+Yjs+REST collab, groups + invites/join E2EE, notifications (list/read; **no** body decrypt), MSW contract smoke, ESLint import walls, **`apps/marketing`** (`vite-ssg`). **Done:** Playwright smoke for **demo** session (httpOnly tokens, `loggedIn` hint, reload → bootstrap/refresh). **Next:** password-login E2E (registered user fixture or flow); optional demo keyring assertions. |
1818
| **5** — Cutover | **Not started** | Canary, retire `/trpc` when safe. |
1919

2020
---
@@ -27,7 +27,7 @@ Cross-check [TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md): **sessions**, **users.a
2727

2828
- **Anonymous public read** for `GET /api/groups/:groupId/pages` (legacy optional auth) — still auth-only; add if product needs it.
2929
- **Stripe:** optional `checkout.session.completed` / richer webhook tests.
30-
- **Marketing `vite-ssg`** site — not in this tree yet (RESTART_PLAN §5.1).
30+
- **Marketing `vite-ssg`** site — `apps/marketing` (static landing + `Open app` via `VITE_WEB_APP_URL`); deploy doc updated in `docs/DEPLOY_CLOUDFLARE.md`.
3131
- **Legacy `scheduler` / `manager` / standalone `collab-server` & `realtime-server` processes** — not ported; greenfield targets Workers + future WS/DO topology instead.
3232

3333
---
@@ -63,6 +63,7 @@ Cross-check [TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md): **sessions**, **users.a
6363
- [x] `/notifications` (ciphertext not decrypted in UI)
6464
- [x] MSW: `GET /api/health`, `GET /api/users/me`; ESLint `no-restricted-imports` on `src/**/*.ts`
6565
- [x] Playwright: demo session against real `wrangler dev` + Vite (see **E2E / Playwright** below)
66+
- [x] Marketing landing (`vite-ssg`): `apps/marketing`
6667
- [ ] Password-login (or registered-user) Playwright path; optional/crypto assertions beyond cookies
6768
- [ ] Capacitor / Tauri after web MVP
6869

@@ -95,6 +96,7 @@ Cross-check [TRPC_REST_MAP.md](./docs/TRPC_REST_MAP.md): **sessions**, **users.a
9596
| `@deepnotes/api` | `openapi.test.ts`, `schemas/users.test.ts`, … | |
9697
| `@deepnotes/api-worker` | **`index.test.ts`****71** route × 503 matrix + **1** extra `email-verification/confirm` 503 | Totals **72** env-missing smoke cases |
9798
| `@deepnotes/web` | Vitest + happy-dom; composable/API tests | MSW contracts; extend login matrix |
99+
| `@deepnotes/marketing` | `vite-ssg build``dist/` | Static landing; `VITE_WEB_APP_URL` for app link |
98100
| **Playwright** | `pnpm test:e2e` (root) → `@deepnotes/web` **1** spec (demo cookies + reload) | Needs migrated DB + `.dev.vars`; CI installs Chromium only |
99101

100102
Run session integration (from repo root):
@@ -109,7 +111,7 @@ Run session integration (from repo root):
109111

110112
- [x] OpenAPI + generated client types; no tRPC/superjson/RevenueCat/key-rotation in this tree; [CLIENT_FORKS](./docs/CLIENT_FORKS.md).
111113
- [x] SQL-heavy flows: template Postgres tests (`@deepnotes/db`, `@deepnotes/session`).
112-
- [ ] Drizzle “empty → current” production upgrade doc.
114+
- [x] Drizzle “empty → current” production upgrade doc ([docs/DRIZZLE_MIGRATIONS.md](./docs/DRIZZLE_MIGRATIONS.md)).
113115
- [ ] Collab **and** **realtime** each: ≥1 integration test with chosen deps.
114116
- [ ] Stripe (and other high-risk paths): deeper automated coverage where secrets allow.
115117
- [ ] Cloudflare staging: Hyperdrive + Postgres + Redis + chosen WS topology load-tested.
@@ -122,7 +124,7 @@ Run session integration (from repo root):
122124

123125
| Date | Change |
124126
|------|--------|
125-
| 2026-04-28 | Playwright demo E2E (`session.spec.ts`), `e2e/dev.vars.ci`, CI: migrate app DB, copy `.dev.vars`, Chromium + `pnpm test:e2e`; root `test:e2e`; tsconfig.node includes playwright + e2e. |
127+
| 2026-04-29 | `apps/marketing` (`vite-ssg` single-page), `docs/DRIZZLE_MIGRATIONS.md`, deploy doc row for marketing + `VITE_WEB_APP_URL`; plan progress. |
126128
| 2026-04-27 | Compacted PLAN_PROGRESS; verified legacy map vs `TRPC_REST_MAP` — HTTP + listed WS flows migrated; added **realtime** vs **collab WS** distinction, optional gaps (public page list, vite-ssg, scheduler). Corrected counts: **20** session integration `it()`, **72** worker 503 smokes (71-route matrix + confirm). |
127129
| 2026-04-27 | MSW contract tests + ESLint restricted imports on web `*.ts`. |
128130
| 2026-04-27 | Tiptap + Yjs editor; invite/join E2EE + crypto bootstrap API; members detail UI; notifications thin SPA; groups overview; collab REST + `passwordSalt`; Stripe. |
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "https://shadcn-vue.com/schema.json",
3+
"style": "reka-nova",
4+
"font": "geist-sans",
5+
"typescript": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "src/styles/globals.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"iconLibrary": "lucide",
14+
"rtl": false,
15+
"aliases": {
16+
"components": "@/components",
17+
"utils": "@/lib/utils",
18+
"ui": "@/components/ui",
19+
"lib": "@/lib",
20+
"composables": "@/composables"
21+
},
22+
"menuColor": "default",
23+
"menuAccent": "subtle",
24+
"registries": {}
25+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import base from "../../eslint.config.js";
2+
3+
export default [...base];
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en" class="dark">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>DeepNotes</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.ts"></script>
11+
</body>
12+
</html>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@deepnotes/marketing",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"build": "vite-ssg build",
8+
"dev": "vite",
9+
"lint": "eslint vite.config.ts \"src/**/*.ts\"",
10+
"test": "node -e \"process.exit(0)\"",
11+
"typecheck": "vue-tsc --noEmit -p tsconfig.json"
12+
},
13+
"dependencies": {
14+
"@tailwindcss/vite": "^4.2.4",
15+
"class-variance-authority": "^0.7.1",
16+
"clsx": "^2.1.1",
17+
"reka-ui": "^2.9.6",
18+
"shadcn-vue": "^2.6.2",
19+
"tailwind-merge": "^3.5.0",
20+
"tailwindcss": "^4.2.4",
21+
"tw-animate-css": "^1.4.0",
22+
"vue": "^3.5.13"
23+
},
24+
"devDependencies": {
25+
"@unhead/vue": "^2.0.19",
26+
"@vitejs/plugin-vue": "^5.2.3",
27+
"typescript": "^5.8.3",
28+
"vite": "^6.3.3",
29+
"vite-ssg": "^28.2.2",
30+
"vue-tsc": "^2.2.10"
31+
}
32+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<template>
2+
<div class="min-h-screen bg-background text-foreground">
3+
<div
4+
class="mx-auto flex min-h-screen max-w-2xl flex-col px-6 py-10 md:px-8"
5+
>
6+
<header class="mb-10 flex items-center justify-between gap-4">
7+
<span class="text-lg font-semibold tracking-tight">DeepNotes</span>
8+
<Button as="a" :href="appHref">Open app</Button>
9+
</header>
10+
11+
<Card class="border-border/60 shadow-none">
12+
<CardHeader class="space-y-3 pb-2">
13+
<CardTitle class="text-2xl tracking-tight sm:text-3xl">
14+
Notes that stay yours
15+
</CardTitle>
16+
<CardDescription class="text-base leading-relaxed">
17+
DeepNotes is built for end-to-end encrypted notes and collaboration. Your content is
18+
encrypted before it leaves your device; the server stores ciphertext only.
19+
</CardDescription>
20+
</CardHeader>
21+
<CardContent class="space-y-4 text-muted-foreground">
22+
<p class="leading-relaxed">
23+
This marketing site is static HTML from
24+
<code class="bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs">vite-ssg</code>;
25+
the signed-in experience is a separate SPA (see
26+
<code class="bg-muted text-foreground rounded px-1.5 py-0.5 font-mono text-xs">apps/web</code>
27+
in the repo).
28+
</p>
29+
</CardContent>
30+
<CardFooter class="flex-col items-start gap-1 pt-4">
31+
<p class="text-muted-foreground text-xs leading-relaxed">
32+
Licensed under AGPL-3.0. Source code obligations apply when you modify and distribute the
33+
software.
34+
</p>
35+
</CardFooter>
36+
</Card>
37+
</div>
38+
</div>
39+
</template>
40+
41+
<script setup lang="ts">
42+
import { computed } from "vue";
43+
import { useHead } from "@unhead/vue";
44+
45+
import { Button } from "@/components/ui/button";
46+
import {
47+
Card,
48+
CardContent,
49+
CardDescription,
50+
CardFooter,
51+
CardHeader,
52+
CardTitle,
53+
} from "@/components/ui/card";
54+
55+
const appHref = computed(() => import.meta.env.VITE_WEB_APP_URL?.trim() || "/");
56+
57+
useHead({
58+
title: "DeepNotes — encrypted notes",
59+
meta: [
60+
{
61+
name: "description",
62+
content:
63+
"End-to-end encrypted notes and collaboration. Static marketing shell; app runs as a separate SPA.",
64+
},
65+
],
66+
});
67+
</script>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
import type { PrimitiveProps } from 'reka-ui'
3+
import type { HTMLAttributes } from 'vue'
4+
import type { ButtonVariants } from '.'
5+
import { Primitive } from 'reka-ui'
6+
import { cn } from '@/lib/utils'
7+
import { buttonVariants } from '.'
8+
9+
interface Props extends PrimitiveProps {
10+
variant?: ButtonVariants['variant']
11+
size?: ButtonVariants['size']
12+
class?: HTMLAttributes['class']
13+
}
14+
15+
const props = withDefaults(defineProps<Props>(), {
16+
as: 'button',
17+
})
18+
</script>
19+
20+
<template>
21+
<Primitive
22+
data-slot="button"
23+
:data-variant="variant"
24+
:data-size="size"
25+
:as="as"
26+
:as-child="asChild"
27+
:class="cn(buttonVariants({ variant, size }), props.class)"
28+
>
29+
<slot />
30+
</Primitive>
31+
</template>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { VariantProps } from 'class-variance-authority'
2+
import { cva } from 'class-variance-authority'
3+
4+
export { default as Button } from './Button.vue'
5+
6+
export const buttonVariants = cva(
7+
'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*=size-])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
8+
{
9+
variants: {
10+
variant: {
11+
default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
12+
outline: 'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground',
13+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
14+
ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',
15+
destructive: 'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',
16+
link: 'text-primary underline-offset-4 hover:underline',
17+
},
18+
size: {
19+
'default': 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
20+
'xs': 'h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*=size-])]:size-3',
21+
'sm': 'h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*=size-])]:size-3.5',
22+
'lg': 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
23+
'icon': 'size-8',
24+
'icon-xs': 'size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*=size-])]:size-3',
25+
'icon-sm': 'size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg',
26+
'icon-lg': 'size-9',
27+
},
28+
},
29+
defaultVariants: {
30+
variant: 'default',
31+
size: 'default',
32+
},
33+
},
34+
)
35+
export type ButtonVariants = VariantProps<typeof buttonVariants>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from 'vue'
3+
import { cn } from '@/lib/utils'
4+
5+
const props = withDefaults(defineProps<{
6+
class?: HTMLAttributes['class']
7+
size?: 'default' | 'sm'
8+
}>(), {
9+
size: 'default',
10+
})
11+
</script>
12+
13+
<template>
14+
<div
15+
data-slot="card"
16+
:data-size="size"
17+
:class="cn('ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-xl py-4 text-sm ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col', props.class)"
18+
>
19+
<slot />
20+
</div>
21+
</template>

0 commit comments

Comments
 (0)