@weft-js/create 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +393 -19
  2. package/bin/create-weft.mjs +62 -14
  3. package/package.json +26 -16
  4. package/scripts/tarballs.mjs +56 -6
  5. package/src/data.mjs +90 -18
  6. package/src/index.mjs +210 -51
  7. package/src/stack/pages.mjs +151 -0
  8. package/src/stack/schema.mjs +195 -0
  9. package/src/stack.mjs +102 -313
  10. package/src/templates.mjs +174 -0
  11. package/src/wizard.mjs +65 -16
  12. package/templates/admin/files/drizzle/.schema-hash +1 -0
  13. package/templates/admin/files/drizzle/0000_admin.sql +136 -0
  14. package/templates/admin/files/drizzle/meta/0000_snapshot.json +771 -0
  15. package/templates/admin/files/drizzle/meta/_journal.json +13 -0
  16. package/templates/admin/files/public/favicon.svg +1 -0
  17. package/templates/admin/files/src/auth.server.ts +24 -0
  18. package/templates/admin/files/src/components/AuthPage.tsx +33 -0
  19. package/templates/admin/files/src/components/Failed.tsx +19 -0
  20. package/templates/admin/files/src/components/Logo.tsx +16 -0
  21. package/templates/admin/files/src/components/OrderDetail.tsx +93 -0
  22. package/templates/admin/files/src/components/OrdersFilters.tsx +44 -0
  23. package/templates/admin/files/src/components/OrdersTable.tsx +144 -0
  24. package/templates/admin/files/src/components/Overview.tsx +113 -0
  25. package/templates/admin/files/src/components/StatusBadge.tsx +12 -0
  26. package/templates/admin/files/src/db/schema.ts +68 -0
  27. package/templates/admin/files/src/lib/orders.ts +60 -0
  28. package/templates/admin/files/src/routes/404.tsx +20 -0
  29. package/templates/admin/files/src/routes/_error.tsx +20 -0
  30. package/templates/admin/files/src/routes/_layout.tsx +6 -0
  31. package/templates/admin/files/src/routes/admin/_layout.tsx +89 -0
  32. package/templates/admin/files/src/routes/admin/index.tsx +45 -0
  33. package/templates/admin/files/src/routes/admin/nested/_layout.tsx +35 -0
  34. package/templates/admin/files/src/routes/admin/nested/index.tsx +77 -0
  35. package/templates/admin/files/src/routes/admin/orders/[id].tsx +24 -0
  36. package/templates/admin/files/src/routes/admin/orders/index.tsx +28 -0
  37. package/templates/admin/files/src/routes/admin/per-row.tsx +81 -0
  38. package/templates/admin/files/src/routes/admin/slow.tsx +74 -0
  39. package/templates/admin/files/src/routes/index.tsx +13 -0
  40. package/templates/admin/files/src/routes/login.tsx +53 -0
  41. package/templates/admin/files/src/routes/signup.tsx +60 -0
  42. package/templates/admin/files/src/server/orders.ts +193 -0
  43. package/templates/admin/files/src/server/reports.ts +92 -0
  44. package/templates/admin/files/src/server/session.ts +42 -0
  45. package/templates/admin/files/src/theme.css +43 -0
  46. package/templates/admin/files/weft.config.ts +31 -0
  47. package/templates/admin/template.json +22 -0
  48. package/templates/catalog/files/drizzle/.schema-hash +1 -0
  49. package/templates/catalog/files/drizzle/0000_catalog.sql +47 -0
  50. package/templates/catalog/files/drizzle/meta/0000_snapshot.json +263 -0
  51. package/templates/catalog/files/drizzle/meta/_journal.json +13 -0
  52. package/templates/catalog/files/public/favicon.svg +1 -0
  53. package/templates/catalog/files/seed.sql +13 -0
  54. package/templates/catalog/files/src/components/AddToCart.tsx +22 -0
  55. package/templates/catalog/files/src/components/Aisles.tsx +31 -0
  56. package/templates/catalog/files/src/components/CartLines.tsx +96 -0
  57. package/templates/catalog/files/src/components/CartLink.tsx +29 -0
  58. package/templates/catalog/files/src/components/Failed.tsx +19 -0
  59. package/templates/catalog/files/src/components/Featured.tsx +19 -0
  60. package/templates/catalog/files/src/components/Filters.tsx +40 -0
  61. package/templates/catalog/files/src/components/Logo.tsx +16 -0
  62. package/templates/catalog/files/src/components/ProductArt.tsx +42 -0
  63. package/templates/catalog/files/src/components/ProductCard.tsx +42 -0
  64. package/templates/catalog/files/src/components/ProductList.tsx +65 -0
  65. package/templates/catalog/files/src/components/Related.tsx +19 -0
  66. package/templates/catalog/files/src/components/SiteFooter.tsx +9 -0
  67. package/templates/catalog/files/src/components/SiteHeader.tsx +16 -0
  68. package/templates/catalog/files/src/db/schema.ts +53 -0
  69. package/templates/catalog/files/src/lib/catalog.ts +35 -0
  70. package/templates/catalog/files/src/routes/404.tsx +18 -0
  71. package/templates/catalog/files/src/routes/_error.tsx +18 -0
  72. package/templates/catalog/files/src/routes/_layout.tsx +14 -0
  73. package/templates/catalog/files/src/routes/c/[slug].tsx +53 -0
  74. package/templates/catalog/files/src/routes/cart.tsx +29 -0
  75. package/templates/catalog/files/src/routes/index.tsx +84 -0
  76. package/templates/catalog/files/src/routes/p/[slug].tsx +67 -0
  77. package/templates/catalog/files/src/server/cart.ts +125 -0
  78. package/templates/catalog/files/src/server/catalog.ts +107 -0
  79. package/templates/catalog/files/src/theme.css +159 -0
  80. package/templates/catalog/files/weft.config.ts +30 -0
  81. package/templates/catalog/template.json +20 -0
  82. package/templates/landing/files/.weft/plugins.lock.json +42 -0
  83. package/templates/landing/files/assets/fonts/LICENSE-figtree.txt +93 -0
  84. package/templates/landing/files/assets/fonts/figtree.woff2 +0 -0
  85. package/templates/landing/files/assets/hero.webp +0 -0
  86. package/templates/landing/files/drizzle/.schema-hash +1 -0
  87. package/templates/landing/files/drizzle/0000_landing.sql +6 -0
  88. package/templates/landing/files/drizzle/meta/0000_snapshot.json +60 -0
  89. package/templates/landing/files/drizzle/meta/_journal.json +13 -0
  90. package/templates/landing/files/public/favicon.svg +1 -0
  91. package/templates/landing/files/src/components/SiteFooter.tsx +24 -0
  92. package/templates/landing/files/src/components/SiteHeader.tsx +23 -0
  93. package/templates/landing/files/src/components/WaitlistForm.tsx +36 -0
  94. package/templates/landing/files/src/db/schema.ts +7 -0
  95. package/templates/landing/files/src/routes/404.tsx +24 -0
  96. package/templates/landing/files/src/routes/_error.tsx +24 -0
  97. package/templates/landing/files/src/routes/_layout.tsx +14 -0
  98. package/templates/landing/files/src/routes/index.tsx +130 -0
  99. package/templates/landing/files/src/routes/joined.tsx +30 -0
  100. package/templates/landing/files/src/routes/pricing.tsx +131 -0
  101. package/templates/landing/files/src/routes/privacy.tsx +56 -0
  102. package/templates/landing/files/src/server/waitlist.ts +13 -0
  103. package/templates/landing/files/src/theme.css +108 -0
  104. package/templates/landing/files/weft.config.ts +44 -0
  105. package/templates/landing/template.json +17 -0
  106. package/templates/todo/files/drizzle/.schema-hash +1 -0
  107. package/templates/todo/files/drizzle/0000_todo.sql +82 -0
  108. package/templates/todo/files/drizzle/meta/0000_snapshot.json +462 -0
  109. package/templates/todo/files/drizzle/meta/_journal.json +13 -0
  110. package/templates/todo/files/public/favicon.svg +1 -0
  111. package/templates/todo/files/src/components/AuthPage.tsx +33 -0
  112. package/templates/todo/files/src/components/Logo.tsx +14 -0
  113. package/templates/todo/files/src/components/NewTodo.tsx +27 -0
  114. package/templates/todo/files/src/components/TodoList.tsx +101 -0
  115. package/templates/todo/files/src/db/schema.ts +18 -0
  116. package/templates/todo/files/src/lib/views.ts +13 -0
  117. package/templates/todo/files/src/routes/404.tsx +20 -0
  118. package/templates/todo/files/src/routes/_error.tsx +20 -0
  119. package/templates/todo/files/src/routes/_layout.tsx +6 -0
  120. package/templates/todo/files/src/routes/index.tsx +13 -0
  121. package/templates/todo/files/src/routes/login.tsx +53 -0
  122. package/templates/todo/files/src/routes/signup.tsx +57 -0
  123. package/templates/todo/files/src/routes/todos.tsx +87 -0
  124. package/templates/todo/files/src/server/todos.ts +98 -0
  125. package/templates/todo/files/src/theme.css +71 -0
  126. package/templates/todo/files/weft.config.ts +30 -0
  127. package/templates/todo/template.json +18 -0
@@ -0,0 +1,20 @@
1
+ import { Link, type Meta } from '@weft-js/start'
2
+ import { Center, Heading, Stack, Text } from '@weft-js/warp'
3
+
4
+ export const meta: Meta = { title: 'Page not found' }
5
+
6
+ export default function NotFound() {
7
+ return (
8
+ <main class="system-page">
9
+ <Center max="narrow" gutter>
10
+ <Stack gap="group" align="center">
11
+ <Heading level={1} role="title">
12
+ That page isn't in the book
13
+ </Heading>
14
+ <Text subtle>The link may be old, or the address mistyped.</Text>
15
+ <Link href="/todos">Back to your list</Link>
16
+ </Stack>
17
+ </Center>
18
+ </main>
19
+ )
20
+ }
@@ -0,0 +1,20 @@
1
+ import { Link, type Meta } from '@weft-js/start'
2
+ import { Center, Heading, Stack, Text } from '@weft-js/warp'
3
+
4
+ export const meta: Meta = { title: 'Something went wrong' }
5
+
6
+ export default function ServerError() {
7
+ return (
8
+ <main class="system-page">
9
+ <Center max="narrow" gutter>
10
+ <Stack gap="group" align="center">
11
+ <Heading level={1} role="title">
12
+ Something went wrong on our side
13
+ </Heading>
14
+ <Text subtle>Your list is safe. Give it a minute and try again.</Text>
15
+ <Link href="/todos">Back to your list</Link>
16
+ </Stack>
17
+ </Center>
18
+ </main>
19
+ )
20
+ }
@@ -0,0 +1,6 @@
1
+ import '../theme.css'
2
+ import type { RouteProps } from '@weft-js/start'
3
+
4
+ export default function Root(props: RouteProps) {
5
+ return <>{props.children}</>
6
+ }
@@ -0,0 +1,13 @@
1
+ import { redirect, type Meta } from '@weft-js/start'
2
+
3
+ // Only ever seen as a redirect, but a link to the bare domain still gets a title and a card.
4
+ export const meta: Meta = {
5
+ title: 'Daybook',
6
+ description: 'Daybook is a plain, private list of what you mean to get done.',
7
+ }
8
+
9
+ export const load = () => redirect('/todos')
10
+
11
+ export default function Home() {
12
+ return null
13
+ }
@@ -0,0 +1,53 @@
1
+ import { Form, Link, redirect, type FormApi, type LoadArgs, type Meta } from '@weft-js/start'
2
+ import { Alert, Button, Input, Stack, Text } from '@weft-js/warp'
3
+ import { AuthPage } from '#/components/AuthPage'
4
+ import { signIn } from '#/server/session'
5
+
6
+ export const load = ({ ctx }: LoadArgs) => {
7
+ if (ctx.user) redirect('/todos')
8
+ }
9
+
10
+ export const meta: Meta = {
11
+ title: 'Sign in',
12
+ description: 'Daybook is a plain, private list of what you mean to get done.',
13
+ }
14
+
15
+ export default function Login() {
16
+ return (
17
+ <AuthPage title="Welcome back" lede="Sign in to pick up where you left off.">
18
+ <Form of={signIn}>
19
+ {(form: FormApi) => (
20
+ <Stack gap="related">
21
+ {form.message.value && (
22
+ <Alert tone="critical" title="We couldn't sign you in" live>
23
+ {form.message.value}
24
+ </Alert>
25
+ )}
26
+ <Input
27
+ label="Email"
28
+ type="email"
29
+ autocomplete="username"
30
+ required
31
+ {...form.field('email', 'email')}
32
+ />
33
+ <form.Error name="email" class="form-error" />
34
+ <Input
35
+ label="Password"
36
+ type="password"
37
+ name="password"
38
+ autocomplete="current-password"
39
+ required
40
+ />
41
+ <form.Error name="password" class="form-error" />
42
+ <Button type="submit" full loading={form.pending.value}>
43
+ Sign in
44
+ </Button>
45
+ </Stack>
46
+ )}
47
+ </Form>
48
+ <Text subtle>
49
+ New to Daybook? <Link href="/signup">Create an account</Link>
50
+ </Text>
51
+ </AuthPage>
52
+ )
53
+ }
@@ -0,0 +1,57 @@
1
+ import { Form, Link, redirect, type FormApi, type LoadArgs, type Meta } from '@weft-js/start'
2
+ import { Alert, Button, Input, Stack, Text } from '@weft-js/warp'
3
+ import { AuthPage } from '#/components/AuthPage'
4
+ import { signUp } from '#/server/session'
5
+
6
+ export const load = ({ ctx }: LoadArgs) => {
7
+ if (ctx.user) redirect('/todos')
8
+ }
9
+
10
+ export const meta: Meta = {
11
+ title: 'Create an account',
12
+ description: "An email and a password. That's all it takes.",
13
+ }
14
+
15
+ export default function Signup() {
16
+ return (
17
+ <AuthPage title="Start your daybook" lede="An email and a password. That's all it takes.">
18
+ <Form of={signUp}>
19
+ {(form: FormApi) => (
20
+ <Stack gap="related">
21
+ {form.message.value && (
22
+ <Alert tone="critical" title="We couldn't create your account" live>
23
+ {form.message.value}
24
+ </Alert>
25
+ )}
26
+ <Input label="Name" autocomplete="name" required {...form.field('name', 'text')} />
27
+ <form.Error name="name" class="form-error" />
28
+ <Input
29
+ label="Email"
30
+ type="email"
31
+ autocomplete="username"
32
+ required
33
+ {...form.field('email', 'email')}
34
+ />
35
+ <form.Error name="email" class="form-error" />
36
+ <Input
37
+ label="Password"
38
+ type="password"
39
+ name="password"
40
+ autocomplete="new-password"
41
+ required
42
+ minlength={8}
43
+ hint="At least 8 characters."
44
+ />
45
+ <form.Error name="password" class="form-error" />
46
+ <Button type="submit" full loading={form.pending.value}>
47
+ Create account
48
+ </Button>
49
+ </Stack>
50
+ )}
51
+ </Form>
52
+ <Text subtle>
53
+ Already have an account? <Link href="/login">Sign in</Link>
54
+ </Text>
55
+ </AuthPage>
56
+ )
57
+ }
@@ -0,0 +1,87 @@
1
+ import { ChecksIcon, CircleIcon, ClipboardListIcon, LogOutIcon } from '@weft-js/warp-icons'
2
+ import { redirect, type LoadArgs, type Meta, type RouteProps } from '@weft-js/start'
3
+ import { Alert, Button, Heading, Shell, Stack, Toaster, toast } from '@weft-js/warp'
4
+ import { Boundary } from '@weft-js/core'
5
+ import { Logo } from '#/components/Logo'
6
+ import { NewTodo } from '#/components/NewTodo'
7
+ import { TodoList, TodoListLoading } from '#/components/TodoList'
8
+ import { viewOf, views } from '#/lib/views'
9
+ import { signOut } from '#/server/session'
10
+
11
+ export const load = ({ ctx, request }: LoadArgs) => {
12
+ if (!ctx.user) redirect('/login')
13
+ return { view: viewOf(request.url), name: ctx.user.name, email: ctx.user.email }
14
+ }
15
+
16
+ export const meta: Meta<typeof load> = ({ data }) => ({ title: views[data.view].title })
17
+
18
+ const nav = [
19
+ {
20
+ items: [
21
+ { id: 'all', label: views.all.label, href: views.all.href, icon: ClipboardListIcon },
22
+ { id: 'active', label: views.active.label, href: views.active.href, icon: CircleIcon },
23
+ { id: 'done', label: views.done.label, href: views.done.href, icon: ChecksIcon },
24
+ ],
25
+ },
26
+ ]
27
+
28
+ const leave = () =>
29
+ signOut().catch(() =>
30
+ toast.show({
31
+ tone: 'critical',
32
+ title: "You're still signed in",
33
+ description: "That didn't go through. Try again in a moment.",
34
+ }),
35
+ )
36
+ const initials = (name: string) =>
37
+ name
38
+ .split(/\s+/)
39
+ .filter(Boolean)
40
+ .slice(0, 2)
41
+ .map((word) => word[0])
42
+ .join('')
43
+ .toUpperCase()
44
+
45
+ export default function Todos(props: RouteProps<typeof load>) {
46
+ return (
47
+ <Shell
48
+ brand={{ name: 'Daybook', href: '/todos', mark: <Logo /> }}
49
+ nav={nav}
50
+ navLabel="Views"
51
+ current={props.data.view}
52
+ account={{
53
+ name: props.data.name,
54
+ email: props.data.email,
55
+ initials: initials(props.data.name),
56
+ items: [{ label: 'Sign out', icon: LogOutIcon, onSelect: leave }],
57
+ }}
58
+ overlays={<Toaster />}
59
+ >
60
+ <Stack gap="group" class="day">
61
+ <Heading level={1} role="title">
62
+ {views[props.data.view].title}
63
+ </Heading>
64
+ <NewTodo />
65
+ <Boundary
66
+ pending={<TodoListLoading />}
67
+ error={(_, retry) => (
68
+ <Alert
69
+ tone="critical"
70
+ title="Your list didn't load"
71
+ live
72
+ actions={
73
+ <Button emphasis="outline" onClick={retry}>
74
+ Try again
75
+ </Button>
76
+ }
77
+ >
78
+ Nothing's lost. It's the connection to your list that failed.
79
+ </Alert>
80
+ )}
81
+ >
82
+ <TodoList view={props.data.view} />
83
+ </Boundary>
84
+ </Stack>
85
+ </Shell>
86
+ )
87
+ }
@@ -0,0 +1,98 @@
1
+ import { mutation, query, type OptimisticCache } from '@weft-js/start'
2
+ import { and, desc, eq } from 'drizzle-orm'
3
+ import { z } from 'zod'
4
+ import { todos } from '#/db/schema'
5
+ import { viewNames, type View } from '#/lib/views'
6
+
7
+ const byId = z.object({ id: z.guid() })
8
+ const shown = { all: undefined, active: eq(todos.done, false), done: eq(todos.done, true) }
9
+
10
+ type List = { rows: { id: string; title: string; done: boolean }[]; left: number }
11
+
12
+ // Rows are per user and change from anywhere, so each view reads what it shows, with the count every view needs.
13
+ export const listTodos = query({
14
+ input: z.object({ view: z.enum(viewNames) }),
15
+ tags: ['todos'],
16
+ auth: 'user',
17
+ run: async ({ input, ctx }): Promise<List> => {
18
+ const mine = eq(todos.userId, ctx.user.id)
19
+ const [rows, left] = await Promise.all([
20
+ ctx.db
21
+ .select({ id: todos.id, title: todos.title, done: todos.done })
22
+ .from(todos)
23
+ .where(and(mine, shown[input.view]))
24
+ .orderBy(desc(todos.createdAt), desc(todos.id)),
25
+ ctx.db.$count(todos, and(mine, eq(todos.done, false))),
26
+ ])
27
+ return { rows, left }
28
+ },
29
+ })
30
+
31
+ /** A change to the views already in the cache: only those are drawn, and any of them can come back into sight. */
32
+ function inViews(cache: OptimisticCache, change: (list: List, view: View) => void) {
33
+ for (const view of viewNames)
34
+ if (cache.get(listTodos, { view }))
35
+ cache.update(listTodos, { view }, (list) => change(list, view))
36
+ }
37
+
38
+ // The client picks the id, so the row it draws is the row that lands, and sending the same add again can't make a second one.
39
+ export const addTodo = mutation({
40
+ input: byId.extend({
41
+ title: z
42
+ .string()
43
+ .trim()
44
+ .min(1, 'Write something first.')
45
+ .max(200, 'Keep it under 200 characters.'),
46
+ }),
47
+ auth: 'user',
48
+ invalidates: ['todos'],
49
+ optimistic: ({ input }, cache) =>
50
+ inViews(cache, (list, view) => {
51
+ list.left++
52
+ if (view !== 'done')
53
+ list.rows.unshift({ id: input.id, title: input.title.trim(), done: false })
54
+ }),
55
+ run: async ({ input, ctx }) => {
56
+ await ctx.db
57
+ .insert(todos)
58
+ .values({ ...input, userId: ctx.user.id })
59
+ .onConflictDoNothing()
60
+ },
61
+ })
62
+
63
+ // `done` is the state asked for, not a flip: a repeated or reordered request still ends where the last click left it.
64
+ export const toggleTodo = mutation({
65
+ input: byId.extend({ done: z.boolean() }),
66
+ auth: 'user',
67
+ invalidates: ['todos'],
68
+ optimistic: ({ input }, cache) =>
69
+ inViews(cache, (list, view) => {
70
+ const row = list.rows.find((t) => t.id === input.id)
71
+ if (!row || row.done === input.done) return
72
+ list.left += input.done ? -1 : 1
73
+ if (view === 'all') row.done = input.done
74
+ else list.rows.splice(list.rows.indexOf(row), 1)
75
+ }),
76
+ run: async ({ input, ctx }) => {
77
+ await ctx.db
78
+ .update(todos)
79
+ .set({ done: input.done })
80
+ .where(and(eq(todos.id, input.id), eq(todos.userId, ctx.user.id)))
81
+ },
82
+ })
83
+
84
+ export const deleteTodo = mutation({
85
+ input: byId,
86
+ auth: 'user',
87
+ invalidates: ['todos'],
88
+ optimistic: ({ input }, cache) =>
89
+ inViews(cache, (list) => {
90
+ const row = list.rows.find((t) => t.id === input.id)
91
+ if (!row) return
92
+ if (!row.done) list.left--
93
+ list.rows.splice(list.rows.indexOf(row), 1)
94
+ }),
95
+ run: async ({ input, ctx }) => {
96
+ await ctx.db.delete(todos).where(and(eq(todos.id, input.id), eq(todos.userId, ctx.user.id)))
97
+ },
98
+ })
@@ -0,0 +1,71 @@
1
+ /*
2
+ * Daybook's own CSS, over warp's tokens. The ink, paper, serif and square corners are warp() options in weft.config.ts;
3
+ * this file is the ruled page. Change those options first, then the rules here.
4
+ */
5
+ body { margin: 0; }
6
+
7
+ /* AuthScreen fills its parent, and here its parent is the page */
8
+ [data-scope="auth"], [data-scope="auth"] > [data-part="layout"] { min-block-size: 100dvh; }
9
+
10
+ /* the brand panel only shows from 1000 up, so narrower pages carry the name themselves */
11
+ .auth-mark { display: flex; align-items: center; gap: var(--warp-space-tight); font-weight: var(--warp-text-heading-weight); }
12
+ @container auth (min-width: 1000px) { .auth-mark { display: none; } }
13
+
14
+ .logo { display: block; inline-size: 1.5rem; block-size: 1.5rem; flex: none; }
15
+
16
+ .day {
17
+ inline-size: 100%;
18
+ max-inline-size: 40rem;
19
+ margin-inline: auto;
20
+ padding-block: var(--warp-space-page);
21
+ }
22
+
23
+ .new-todo { display: grid; gap: var(--warp-space-tight); }
24
+ .new-todo-row { display: flex; align-items: end; gap: var(--warp-space-related); }
25
+ .new-todo-row [data-scope="field"] { flex: 1; min-inline-size: 0; }
26
+ .new-todo-row > [data-scope="button"] { block-size: var(--warp-field-height); }
27
+
28
+ .form-error { margin: 0; color: var(--warp-color-critical-text); font-size: var(--warp-text-caption-size); }
29
+ .form-error:empty { display: none; }
30
+
31
+ .tally { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: var(--warp-space-group); }
32
+ .tally [data-part="label-row"] { display: none; }
33
+
34
+ /* the ruled page: a hairline under each entry, and a margin rule down the left */
35
+ .todos {
36
+ margin: 0;
37
+ padding: 0;
38
+ list-style: none;
39
+ background: var(--warp-color-surface);
40
+ border-block-start: 2px solid var(--warp-color-accent-solid);
41
+ box-shadow: inset 2.75rem 0 0 -2.6875rem var(--warp-color-accent-border);
42
+ }
43
+ .todo {
44
+ display: flex;
45
+ align-items: center;
46
+ justify-content: space-between;
47
+ gap: var(--warp-space-related);
48
+ padding: var(--warp-space-tight) var(--warp-space-related);
49
+ border-block-end: 1px solid var(--warp-color-border);
50
+ /* A row off screen isn't laid out until it nears the view: a list of 200 costs the few that show. */
51
+ content-visibility: auto;
52
+ contain-intrinsic-size: auto 3rem;
53
+ }
54
+ .todo [data-scope="checkbox"] { flex: 1; min-inline-size: 0; overflow-wrap: anywhere; }
55
+ .todo:has(input:checked) [data-part="label"] { color: var(--warp-color-text-subtle); text-decoration: line-through; }
56
+ @media (hover: hover) {
57
+ .todo > [data-scope="button"] { opacity: 0; }
58
+ .todo:hover > [data-scope="button"], .todo:focus-within > [data-scope="button"] { opacity: 1; }
59
+ }
60
+
61
+ .blank {
62
+ display: grid;
63
+ justify-items: center;
64
+ gap: var(--warp-space-tight);
65
+ padding: var(--warp-space-page) var(--warp-space-group);
66
+ text-align: center;
67
+ border: 1px dashed var(--warp-color-border-strong);
68
+ }
69
+ .blank > [data-icon] { inline-size: 2rem; block-size: 2rem; color: var(--warp-color-accent-text); }
70
+
71
+ .system-page { display: grid; place-items: center; min-block-size: 100dvh; background: var(--warp-color-page); }
@@ -0,0 +1,30 @@
1
+ import { defineConfig } from '@weft-js/core/config'
2
+ import { warp } from '@weft-js/warp/plugin'
3
+
4
+ export default defineConfig({
5
+ site: {
6
+ name: 'Daybook',
7
+ locale: 'en_US',
8
+ title: '%s · Daybook',
9
+ description: 'Daybook is a plain, private list of what you mean to get done.',
10
+ head: '<link rel="icon" href="/favicon.svg" type="image/svg+xml">',
11
+ },
12
+ plugins: [
13
+ warp({
14
+ seed: { accent: '#1f6f5c' },
15
+ dials: { expression: 'quiet', density: 'cozy' },
16
+ radius: 'sharp',
17
+ fields: 'classic',
18
+ fonts: {
19
+ display: '"Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif',
20
+ body: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
21
+ },
22
+ tokens: {
23
+ '--warp-color-page': 'light-dark(#f2f4ef, #121815)',
24
+ '--warp-color-surface': 'light-dark(#f9faf7, #18201c)',
25
+ '--warp-color-surface-raised': 'light-dark(#fcfdfb, #1a221f)',
26
+ '--warp-color-surface-sunken': 'light-dark(#eef1eb, #0e1311)',
27
+ },
28
+ }),
29
+ ],
30
+ })
@@ -0,0 +1,18 @@
1
+ {
2
+ "requires": { "db": "postgres", "auth": true },
3
+ "pages": ["/", "/login", "/signup", "/todos"],
4
+ "dependencies": { "@weft-js/warp-icons": "0.0.0" },
5
+ "warp": {
6
+ "seed": { "accent": "#1f6f5c" },
7
+ "dials": { "expression": "quiet", "density": "cozy" },
8
+ "radius": "sharp",
9
+ "fields": "classic",
10
+ "fonts": { "display": "\"Iowan Old Style\", \"Palatino Linotype\", Palatino, Georgia, serif", "body": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif" },
11
+ "tokens": {
12
+ "--warp-color-page": "light-dark(#f2f4ef, #121815)",
13
+ "--warp-color-surface": "light-dark(#f9faf7, #18201c)",
14
+ "--warp-color-surface-raised": "light-dark(#fcfdfb, #1a221f)",
15
+ "--warp-color-surface-sunken": "light-dark(#eef1eb, #0e1311)"
16
+ }
17
+ }
18
+ }