@montytools/cli 0.5.4 → 0.5.6
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.
- package/bin/monty.mjs +606 -677
- package/lib/compile.mjs +3 -3
- package/lib/schemaCodegen.mjs +7 -3
- package/lib/schemaPull.mjs +36 -50
- package/lib/styleLint.mjs +175 -0
- package/lib/tokens-manifest.mjs +90 -0
- package/lib/views.mjs +19 -0
- package/package.json +3 -3
- package/skills/monty-build/SKILL.md +92 -105
- package/skills/monty-design/SKILL.md +104 -55
- package/template/AGENTS.md +53 -33
- package/template/index.html +5 -5
- package/template/package.json +2 -2
- package/template/src/components/ui/badge.tsx +1 -1
- package/template/src/components/ui/button.tsx +7 -7
- package/template/src/components/ui/card.tsx +3 -3
- package/template/src/components/ui/empty.tsx +3 -3
- package/template/src/components/ui/field.tsx +5 -5
- package/template/src/components/ui/input.tsx +1 -1
- package/template/src/components/ui/label.tsx +1 -1
- package/template/src/components/ui/select.tsx +4 -4
- package/template/src/components/ui/table.tsx +3 -3
- package/template/src/index.css +7 -118
- package/template/src/main.tsx +1 -1
- package/template/src/monty.gen.ts +16 -0
- package/template/src/routes/index.tsx +1 -1
- package/template/tsconfig.json +1 -1
- package/template/monty.config.ts +0 -21
package/template/AGENTS.md
CHANGED
|
@@ -4,13 +4,13 @@ Monty is a work OS: your app runs inside a team's workspace, on shared reactive
|
|
|
4
4
|
data, with auth and deployment handled by the platform. **You only write product
|
|
5
5
|
logic.** Everything below is the complete contract.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`monty current` confirms which app folder you are in. Never create app folders by hand — `monty create` and `monty connect` do it.
|
|
8
8
|
|
|
9
9
|
## The three files that matter
|
|
10
10
|
|
|
11
11
|
| File | What it is |
|
|
12
12
|
|---|---|
|
|
13
|
-
| `monty.
|
|
13
|
+
| `src/monty.gen.ts` | The app's schema, GENERATED from the workspace config — never edit it (it is overwritten on every sync). Import `{ app }` from it for typed hooks. Change the schema with `monty schema set` (read it first with `monty schema`); a running `monty dev` regenerates this file within a heartbeat. |
|
|
14
14
|
| `src/routes/` | Your UI — TanStack Router file routes (`index.tsx` = `/`). The starter `index.tsx` is a blank-canvas placeholder — replace it with the real app. |
|
|
15
15
|
| `src/main.tsx` | Wiring. Do not edit. |
|
|
16
16
|
|
|
@@ -24,22 +24,33 @@ Folders are managed for you: this app lives in `~/Monty/<slug>`. `monty current`
|
|
|
24
24
|
3. **UI is shadcn/ui, preconfigured — never build components from scratch.**
|
|
25
25
|
Before building any UI piece, run `monty components`. If the capability is
|
|
26
26
|
listed (data tables, kanban, calendar, combobox, file upload, rich text,
|
|
27
|
-
charts, …) install the curated implementation with
|
|
28
|
-
core shadcn components install by bare
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
charts, …) install the curated implementation with
|
|
28
|
+
`monty components add <name>`; core shadcn components install by bare
|
|
29
|
+
name (`monty components add dialog tabs`). Everything lands in
|
|
30
|
+
`src/components/ui/*` already carrying the Monty theme.
|
|
31
|
+
`monty components docs <name>` shows a component's source before installing.
|
|
31
32
|
Icons from `lucide-react`. Don't install other component libraries or
|
|
32
33
|
write raw-color CSS — use semantic tokens (`bg-background`,
|
|
33
|
-
`text-muted-foreground`, …). Don't edit `src/index.css
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
`text-muted-foreground`, …). Don't edit `src/index.css`; the design
|
|
35
|
+
tokens come from `@montytools/sdk/tokens.css` — the same sheet the Monty
|
|
36
|
+
shell runs on, so styling with tokens IS what makes the app look native.
|
|
37
|
+
Text is the six-step ladder (`text-tick/meta/body/title/heading/stat`);
|
|
38
|
+
`text-body` is the default and already on `body`, so most text needs NO
|
|
39
|
+
size class at all — the fewer styles a page states, the more native it
|
|
40
|
+
looks. Never arbitrary values (`text-[15px]`, `bg-[#…]`, `rounded-[…]`).
|
|
41
|
+
The look is Lyra: rectilinear — surfaces AND controls are stock
|
|
42
|
+
components, sharp-cornered by policy (`rounded-full` on dots/avatars is
|
|
43
|
+
the one exception), never hand-styled divs; chart marks are sharp
|
|
36
44
|
rectangles on real axes. The `monty-design` skill is the full spec.
|
|
37
|
-
4. **Schema changes
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
4. **Schema changes go through the door, never a file.** Read the config
|
|
46
|
+
with `monty schema` (pure JSON on stdout), edit it, write it back whole
|
|
47
|
+
with `monty schema set '<json>'` (or pipe: `monty schema set -`) —
|
|
48
|
+
validated, CAS-guarded, live in the workspace within seconds, and
|
|
49
|
+
`src/monty.gen.ts` (your typed view) regenerates automatically while
|
|
50
|
+
`monty dev` runs. Prefer additive changes; give new fields defaults or
|
|
51
|
+
mark them optional so existing records stay readable. Describe fields
|
|
52
|
+
for the next agent that fills them in (`description` on the field spec;
|
|
53
|
+
enum options say WHEN they apply, not what the word means).
|
|
43
54
|
5. **You are not done until the work is saved.** After every meaningful
|
|
44
55
|
change verified in dev, run `monty save "<what changed>"` — it builds,
|
|
45
56
|
typechecks, and pushes the working copy to the cloud copy, like
|
|
@@ -47,8 +58,12 @@ Folders are managed for you: this app lives in `~/Monty/<slug>`. `monty current`
|
|
|
47
58
|
|
|
48
59
|
## Data: define, then use
|
|
49
60
|
|
|
61
|
+
`src/monty.gen.ts` mirrors the workspace config as exactly this shape —
|
|
62
|
+
zod tables wrapped in `defineApp` — so this is what your typed `app`
|
|
63
|
+
object looks like (generated; declare the real thing via `monty schema set`):
|
|
64
|
+
|
|
50
65
|
```ts
|
|
51
|
-
// monty.
|
|
66
|
+
// src/monty.gen.ts (GENERATED — read, never edit)
|
|
52
67
|
import { defineApp, montyFileSchema, montySelect } from "@montytools/sdk";
|
|
53
68
|
import { z } from "zod";
|
|
54
69
|
|
|
@@ -72,7 +87,7 @@ export const app = defineApp({
|
|
|
72
87
|
|
|
73
88
|
```tsx
|
|
74
89
|
import { useList, useRecord, useInsert, useUpdate, useRemove, useMembers, useUploadFile, useFileUrl } from "@montytools/sdk/react";
|
|
75
|
-
import { app } from "
|
|
90
|
+
import { app } from "../monty.gen";
|
|
76
91
|
|
|
77
92
|
const { data, status, loadMore } = useList(app, "expenses", {
|
|
78
93
|
filter: { status: "submitted" }, // equality on schema fields only
|
|
@@ -180,14 +195,14 @@ Rules that matter:
|
|
|
180
195
|
```tsx
|
|
181
196
|
import { PageHeader, PageHeaderButton } from "@montytools/sdk/ui";
|
|
182
197
|
|
|
183
|
-
<div className="flex h-full min-h-dvh flex-col">
|
|
198
|
+
<div className="flex h-full min-h-dvh flex-col bg-background">
|
|
184
199
|
<PageHeader icon={ChartColumn} title="Reports" meta="42 rows">
|
|
185
200
|
<PageHeaderButton onClick={exportCsv}>Export</PageHeaderButton>
|
|
186
201
|
<PageHeaderButton primary onClick={openNew}>
|
|
187
202
|
<Plus className="size-3.5" /> New
|
|
188
203
|
</PageHeaderButton>
|
|
189
204
|
</PageHeader>
|
|
190
|
-
<main className="min-h-0 flex-1 overflow-auto p-
|
|
205
|
+
<main className="min-h-0 flex-1 overflow-auto p-page">…</main>
|
|
191
206
|
</div>
|
|
192
207
|
```
|
|
193
208
|
|
|
@@ -234,7 +249,7 @@ Every platform error is one line shaped like:
|
|
|
234
249
|
| Code | Meaning |
|
|
235
250
|
|---|---|
|
|
236
251
|
| `VALIDATION` | Payload doesn't match your zod schema (unknown fields are also an error) — fix the payload or the schema. |
|
|
237
|
-
| `UNKNOWN_TABLE` | Table name not in
|
|
252
|
+
| `UNKNOWN_TABLE` | Table name not in the workspace config's `tables` (`monty schema` shows it). |
|
|
238
253
|
| `NOT_FOUND` | Stale, foreign, or WRONG-TABLE record id — ids come from `useList`/`useRecord` on the same table; never hard-code or mix them. |
|
|
239
254
|
| `INVALID_SCHEMA` | A table field uses a reserved name (`_*`, `updatedAt`, `createdBy`) — rename it. |
|
|
240
255
|
| `SCHEMA_DRIFT` (warning) | Stored rows predate your latest schema change; nothing crashes, but make changed fields `.optional()`/`.default(...)`. |
|
|
@@ -242,7 +257,7 @@ Every platform error is one line shaped like:
|
|
|
242
257
|
| `UNAUTHENTICATED` / `NO_ACTIVE_WORKSPACE` | App isn't running through the Monty host/dev shell. |
|
|
243
258
|
| `MISSING_ENV` / `NO_PROVIDER` | `.env.local` or the `<MontyProvider>` in `main.tsx` was removed. |
|
|
244
259
|
|
|
245
|
-
## Server code (optional): functions,
|
|
260
|
+
## Server code (optional): functions, shared addresses, clock rules
|
|
246
261
|
|
|
247
262
|
When logic must not run in the browser (private tables, third-party APIs with
|
|
248
263
|
secret keys, webhooks, clocks), create `server/index.ts` with named async
|
|
@@ -253,7 +268,7 @@ tables, including ones the UI never exposes), `ctx.files` (platform file
|
|
|
253
268
|
storage: `upload(data, {name?, contentType?}) → MontyFile`,
|
|
254
269
|
`download(fileOrId) → Blob`, `remove(fileOrId)` — same descriptors as the
|
|
255
270
|
`useUploadFile` hook, 10MB cap), `ctx.secrets` (see below), `ctx.viewer`
|
|
256
|
-
(who called: member session/visitor/
|
|
271
|
+
(who called: member session/visitor/rule/none), and `ctx.track()`
|
|
257
272
|
(emit an event).
|
|
258
273
|
|
|
259
274
|
```ts
|
|
@@ -266,8 +281,10 @@ export async function score(args: { sessionId: string }, ctx: MontyFnContext) {
|
|
|
266
281
|
return { verdict: weights.length > 0 ? "ok" : "empty" };
|
|
267
282
|
}
|
|
268
283
|
|
|
269
|
-
// 2)
|
|
270
|
-
//
|
|
284
|
+
// 2) SHARED OUTWARD — after `monty save` ships it, run
|
|
285
|
+
// `monty public set stripe` (the whole list; it replaces) and ANYONE on
|
|
286
|
+
// the internet can call GET/POST /__monty/public/stripe — instantly, no
|
|
287
|
+
// second save.
|
|
271
288
|
// Verify a signature from ctx.secrets against the RAW req.body before
|
|
272
289
|
// trusting anything; return { status, body?, contentType? } to control the
|
|
273
290
|
// response (or return data for 200 JSON, or nothing for {"ok":true}).
|
|
@@ -279,10 +296,12 @@ export async function stripe(req: MontyPublicRequest, ctx: MontyFnContext) {
|
|
|
279
296
|
return { status: 200, body: "ok" };
|
|
280
297
|
}
|
|
281
298
|
|
|
282
|
-
// 3)
|
|
283
|
-
// (
|
|
284
|
-
//
|
|
285
|
-
|
|
299
|
+
// 3) ON THE CLOCK — wire an `every` rule after the save ships the function
|
|
300
|
+
// (rules door / MCP rules_update): { name: "daily-digest", on: "every",
|
|
301
|
+
// cron: "0 9 * * *", run: "digest" } (5-field cron, UTC; max 3 every
|
|
302
|
+
// rules). The platform fires it as digest({ event }, ctx) with
|
|
303
|
+
// event = { type: "every", cron }. Make it idempotent — re-runs happen.
|
|
304
|
+
export async function digest(args: { event?: { cron?: string } }, ctx: MontyFnContext) { /* … */ }
|
|
286
305
|
|
|
287
306
|
// 4) onEvent — reserved name: the platform calls it after every accepted
|
|
288
307
|
// track() event. Forward to Slack/Meta/PostHog/your CRM here, with your keys.
|
|
@@ -290,16 +309,17 @@ export async function onEvent(event: { name: string }, ctx: MontyFnContext) { /*
|
|
|
290
309
|
```
|
|
291
310
|
|
|
292
311
|
From the UI: `const score = useServerFn<Result>(app, "score"); await score({ sessionId })`.
|
|
293
|
-
|
|
294
|
-
`/__monty/public/<name>` URL (and vice versa:
|
|
295
|
-
public).
|
|
312
|
+
Shared (public) functions are NOT callable through `useServerFn` — only at
|
|
313
|
+
their `/__monty/public/<name>` URL (and vice versa: unshared functions are
|
|
314
|
+
never public). `monty public` lists what is shared.
|
|
296
315
|
|
|
297
316
|
**Secrets:** `monty secret set STRIPE_KEY` stores a key for Live (write-only,
|
|
298
317
|
never in code or config). In a dev session, put the same names in the
|
|
299
318
|
gitignored `.monty/secrets.json`. Both arrive as `ctx.secrets.STRIPE_KEY`.
|
|
300
319
|
|
|
301
|
-
**Session behavior:** `monty dev` runs
|
|
302
|
-
|
|
320
|
+
**Session behavior:** while `monty dev` runs, the platform dispatches your
|
|
321
|
+
`every` rules to this session (each firing journals in the app's rule runs)
|
|
322
|
+
and serves shared functions at
|
|
303
323
|
`http://localhost:<port>/__monty/public/<name>` — curl them to test.
|
|
304
324
|
|
|
305
325
|
## Dev loop
|
package/template/index.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
<div id="root">
|
|
10
10
|
<!-- Boot splash: the first paint, shown while the JS loads. React
|
|
11
11
|
replaces it with the SDK's identical MontySplash, so a cold open
|
|
12
|
-
is one continuous visual.
|
|
13
|
-
|
|
12
|
+
is one continuous visual. The background matches the platform's
|
|
13
|
+
dark --background (raw hex is unavoidable pre-CSS). -->
|
|
14
14
|
<style>
|
|
15
15
|
@keyframes monty-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .25 } }
|
|
16
16
|
</style>
|
|
17
|
-
<div style="display: grid; place-items: center; min-height: 100vh; background:
|
|
18
|
-
<div style="width: 10px; height: 10px; border-radius: 999px; background: #
|
|
17
|
+
<div style="display: grid; place-items: center; min-height: 100vh; background: #101112">
|
|
18
|
+
<div style="width: 10px; height: 10px; border-radius: 999px; background: #666; animation: monty-pulse 1.2s ease-in-out infinite"></div>
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
21
|
<script type="module" src="/src/main.tsx"></script>
|
package/template/package.json
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"typecheck": "tsc --noEmit"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@fontsource-variable/
|
|
13
|
-
"@montytools/sdk": "^0.2.
|
|
12
|
+
"@fontsource-variable/inter": "^5.3.0",
|
|
13
|
+
"@montytools/sdk": "^0.2.6",
|
|
14
14
|
"@tanstack/react-router": "1.170.17",
|
|
15
15
|
"class-variance-authority": "^0.7.1",
|
|
16
16
|
"clsx": "^2.1.1",
|
|
@@ -5,7 +5,7 @@ import { Slot } from "radix-ui"
|
|
|
5
5
|
import { cn } from "@/lib/utils"
|
|
6
6
|
|
|
7
7
|
const badgeVariants = cva(
|
|
8
|
-
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-none border border-transparent px-2 py-0.5 text-
|
|
8
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-none border border-transparent px-2 py-0.5 text-meta font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
@@ -5,13 +5,13 @@ import { Slot } from "radix-ui"
|
|
|
5
5
|
import { cn } from "@/lib/utils"
|
|
6
6
|
|
|
7
7
|
const buttonVariants = cva(
|
|
8
|
-
"group/button inline-flex shrink-0 items-center justify-center rounded-
|
|
8
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-control border border-transparent bg-clip-padding text-body font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/
|
|
12
|
+
default: "bg-primary text-primary-foreground shadow-btn-primary hover:bg-primary/90",
|
|
13
13
|
outline:
|
|
14
|
-
"border-
|
|
14
|
+
"border-transparent bg-background shadow-control hover:bg-accent hover:text-foreground aria-expanded:bg-accent aria-expanded:text-foreground",
|
|
15
15
|
secondary:
|
|
16
16
|
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
17
17
|
ghost:
|
|
@@ -23,12 +23,12 @@ const buttonVariants = cva(
|
|
|
23
23
|
size: {
|
|
24
24
|
default:
|
|
25
25
|
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
26
|
-
xs: "h-6 gap-1 rounded-
|
|
27
|
-
sm: "h-7 gap-1 rounded-
|
|
26
|
+
xs: "h-6 gap-1 rounded-control-sm px-2 text-meta has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
27
|
+
sm: "h-7 gap-1 rounded-control px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
28
28
|
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
29
29
|
icon: "size-8",
|
|
30
|
-
"icon-xs": "size-6 rounded-
|
|
31
|
-
"icon-sm": "size-7 rounded-
|
|
30
|
+
"icon-xs": "size-6 rounded-control-sm [&_svg:not([class*='size-'])]:size-3",
|
|
31
|
+
"icon-sm": "size-7 rounded-control",
|
|
32
32
|
"icon-lg": "size-9",
|
|
33
33
|
},
|
|
34
34
|
},
|
|
@@ -12,7 +12,7 @@ function Card({
|
|
|
12
12
|
data-slot="card"
|
|
13
13
|
data-size={size}
|
|
14
14
|
className={cn(
|
|
15
|
-
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-none bg-card py-(--card-spacing) text-
|
|
15
|
+
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-none bg-card py-(--card-spacing) text-body text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none",
|
|
16
16
|
className
|
|
17
17
|
)}
|
|
18
18
|
{...props}
|
|
@@ -38,7 +38,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
38
38
|
<div
|
|
39
39
|
data-slot="card-title"
|
|
40
40
|
className={cn(
|
|
41
|
-
"font-heading text-
|
|
41
|
+
"font-heading text-title group-data-[size=sm]/card:text-title",
|
|
42
42
|
className
|
|
43
43
|
)}
|
|
44
44
|
{...props}
|
|
@@ -50,7 +50,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
50
50
|
return (
|
|
51
51
|
<div
|
|
52
52
|
data-slot="card-description"
|
|
53
|
-
className={cn("text-
|
|
53
|
+
className={cn("text-meta text-muted-foreground", className)}
|
|
54
54
|
{...props}
|
|
55
55
|
/>
|
|
56
56
|
)
|
|
@@ -59,7 +59,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
59
59
|
return (
|
|
60
60
|
<div
|
|
61
61
|
data-slot="empty-title"
|
|
62
|
-
className={cn("font-heading text-
|
|
62
|
+
className={cn("font-heading text-title", className)}
|
|
63
63
|
{...props}
|
|
64
64
|
/>
|
|
65
65
|
)
|
|
@@ -70,7 +70,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
70
70
|
<div
|
|
71
71
|
data-slot="empty-description"
|
|
72
72
|
className={cn(
|
|
73
|
-
"text-
|
|
73
|
+
"text-meta text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
74
74
|
className
|
|
75
75
|
)}
|
|
76
76
|
{...props}
|
|
@@ -83,7 +83,7 @@ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
83
83
|
<div
|
|
84
84
|
data-slot="empty-content"
|
|
85
85
|
className={cn(
|
|
86
|
-
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-
|
|
86
|
+
"flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-body text-balance",
|
|
87
87
|
className
|
|
88
88
|
)}
|
|
89
89
|
{...props}
|
|
@@ -28,7 +28,7 @@ function FieldLegend({
|
|
|
28
28
|
data-slot="field-legend"
|
|
29
29
|
data-variant={variant}
|
|
30
30
|
className={cn(
|
|
31
|
-
"mb-2.5 font-medium data-[variant=label]:text-
|
|
31
|
+
"mb-2.5 font-medium data-[variant=label]:text-meta data-[variant=legend]:text-title",
|
|
32
32
|
className
|
|
33
33
|
)}
|
|
34
34
|
{...props}
|
|
@@ -118,7 +118,7 @@ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
118
118
|
<div
|
|
119
119
|
data-slot="field-label"
|
|
120
120
|
className={cn(
|
|
121
|
-
"flex w-fit items-center gap-2 text-
|
|
121
|
+
"flex w-fit items-center gap-2 text-body group-data-[disabled=true]/field:opacity-50",
|
|
122
122
|
className
|
|
123
123
|
)}
|
|
124
124
|
{...props}
|
|
@@ -131,7 +131,7 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
|
131
131
|
<p
|
|
132
132
|
data-slot="field-description"
|
|
133
133
|
className={cn(
|
|
134
|
-
"text-left text-
|
|
134
|
+
"text-left text-meta leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
|
|
135
135
|
"last:mt-0 nth-last-2:-mt-1",
|
|
136
136
|
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
137
137
|
className
|
|
@@ -153,7 +153,7 @@ function FieldSeparator({
|
|
|
153
153
|
data-slot="field-separator"
|
|
154
154
|
data-content={!!children}
|
|
155
155
|
className={cn(
|
|
156
|
-
"relative -my-2 h-5 text-
|
|
156
|
+
"relative -my-2 h-5 text-meta group-data-[variant=outline]/field-group:-mb-2",
|
|
157
157
|
className
|
|
158
158
|
)}
|
|
159
159
|
{...props}
|
|
@@ -214,7 +214,7 @@ function FieldError({
|
|
|
214
214
|
<div
|
|
215
215
|
role="alert"
|
|
216
216
|
data-slot="field-error"
|
|
217
|
-
className={cn("text-
|
|
217
|
+
className={cn("text-meta font-normal text-destructive", className)}
|
|
218
218
|
{...props}
|
|
219
219
|
>
|
|
220
220
|
{content}
|
|
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
|
8
8
|
type={type}
|
|
9
9
|
data-slot="input"
|
|
10
10
|
className={cn(
|
|
11
|
-
"h-8 w-full min-w-0 rounded-
|
|
11
|
+
"h-8 w-full min-w-0 rounded-control border border-input bg-transparent px-2.5 py-1 text-body transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-body file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-body dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
|
|
12
12
|
className
|
|
13
13
|
)}
|
|
14
14
|
{...props}
|
|
@@ -11,7 +11,7 @@ function Label({
|
|
|
11
11
|
<LabelPrimitive.Root
|
|
12
12
|
data-slot="label"
|
|
13
13
|
className={cn(
|
|
14
|
-
"flex items-center gap-2 text-
|
|
14
|
+
"flex items-center gap-2 text-meta leading-none select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
15
15
|
className
|
|
16
16
|
)}
|
|
17
17
|
{...props}
|
|
@@ -44,7 +44,7 @@ function SelectTrigger({
|
|
|
44
44
|
data-slot="select-trigger"
|
|
45
45
|
data-size={size}
|
|
46
46
|
className={cn(
|
|
47
|
-
"flex w-fit items-center justify-between gap-1.5 rounded-
|
|
47
|
+
"flex w-fit items-center justify-between gap-1.5 rounded-control border border-input bg-transparent py-2 pr-2 pl-2.5 text-body whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-control *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
48
48
|
className
|
|
49
49
|
)}
|
|
50
50
|
{...props}
|
|
@@ -69,7 +69,7 @@ function SelectContent({
|
|
|
69
69
|
<SelectPrimitive.Content
|
|
70
70
|
data-slot="select-content"
|
|
71
71
|
data-align-trigger={position === "item-aligned"}
|
|
72
|
-
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-
|
|
72
|
+
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-overlay bg-popover text-popover-foreground shadow-menu duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
|
|
73
73
|
position={position}
|
|
74
74
|
align={align}
|
|
75
75
|
{...props}
|
|
@@ -97,7 +97,7 @@ function SelectLabel({
|
|
|
97
97
|
return (
|
|
98
98
|
<SelectPrimitive.Label
|
|
99
99
|
data-slot="select-label"
|
|
100
|
-
className={cn("px-2 py-2 text-
|
|
100
|
+
className={cn("px-2 py-2 text-meta text-muted-foreground", className)}
|
|
101
101
|
{...props}
|
|
102
102
|
/>
|
|
103
103
|
)
|
|
@@ -112,7 +112,7 @@ function SelectItem({
|
|
|
112
112
|
<SelectPrimitive.Item
|
|
113
113
|
data-slot="select-item"
|
|
114
114
|
className={cn(
|
|
115
|
-
"relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-
|
|
115
|
+
"relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-body outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
116
116
|
className
|
|
117
117
|
)}
|
|
118
118
|
{...props}
|
|
@@ -10,7 +10,7 @@ function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
|
10
10
|
>
|
|
11
11
|
<table
|
|
12
12
|
data-slot="table"
|
|
13
|
-
className={cn("w-full caption-bottom text-
|
|
13
|
+
className={cn("w-full caption-bottom text-body", className)}
|
|
14
14
|
{...props}
|
|
15
15
|
/>
|
|
16
16
|
</div>
|
|
@@ -68,7 +68,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
|
68
68
|
<th
|
|
69
69
|
data-slot="table-head"
|
|
70
70
|
className={cn(
|
|
71
|
-
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
|
|
71
|
+
"h-10 px-2 text-left align-middle text-meta font-medium whitespace-nowrap text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
72
72
|
className
|
|
73
73
|
)}
|
|
74
74
|
{...props}
|
|
@@ -96,7 +96,7 @@ function TableCaption({
|
|
|
96
96
|
return (
|
|
97
97
|
<caption
|
|
98
98
|
data-slot="table-caption"
|
|
99
|
-
className={cn("mt-4 text-
|
|
99
|
+
className={cn("mt-4 text-meta text-muted-foreground", className)}
|
|
100
100
|
{...props}
|
|
101
101
|
/>
|
|
102
102
|
)
|
package/template/src/index.css
CHANGED
|
@@ -1,138 +1,27 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@import "tw-animate-css";
|
|
3
3
|
@import "shadcn/tailwind.css";
|
|
4
|
-
@import "@fontsource-variable/
|
|
4
|
+
@import "@fontsource-variable/inter";
|
|
5
|
+
/* The Monty design tokens — colors, type ladder, radius policy, elevation.
|
|
6
|
+
The ONE place style values live; nothing in an app restates them. */
|
|
7
|
+
@import "@montytools/sdk/tokens.css";
|
|
5
8
|
|
|
6
9
|
/* Shared platform chrome (@montytools/sdk/ui) ships Tailwind classes in its
|
|
7
10
|
dist — scan it so they compile. */
|
|
8
|
-
@source "../node_modules/@montytools/sdk/dist
|
|
11
|
+
@source "../node_modules/@montytools/sdk/dist";
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
@custom-variant dark (&:is(.dark *));
|
|
12
15
|
|
|
13
|
-
@theme inline {
|
|
14
|
-
--font-heading: var(--font-sans);
|
|
15
|
-
--font-sans: 'Roboto Variable', sans-serif;
|
|
16
|
-
--color-sidebar-ring: var(--sidebar-ring);
|
|
17
|
-
--color-sidebar-border: var(--sidebar-border);
|
|
18
|
-
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
19
|
-
--color-sidebar-accent: var(--sidebar-accent);
|
|
20
|
-
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
21
|
-
--color-sidebar-primary: var(--sidebar-primary);
|
|
22
|
-
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
23
|
-
--color-sidebar: var(--sidebar);
|
|
24
|
-
--color-chart-5: var(--chart-5);
|
|
25
|
-
--color-chart-4: var(--chart-4);
|
|
26
|
-
--color-chart-3: var(--chart-3);
|
|
27
|
-
--color-chart-2: var(--chart-2);
|
|
28
|
-
--color-chart-1: var(--chart-1);
|
|
29
|
-
--color-ring: var(--ring);
|
|
30
|
-
--color-input: var(--input);
|
|
31
|
-
--color-border: var(--border);
|
|
32
|
-
--color-destructive: var(--destructive);
|
|
33
|
-
--color-accent-foreground: var(--accent-foreground);
|
|
34
|
-
--color-accent: var(--accent);
|
|
35
|
-
--color-muted-foreground: var(--muted-foreground);
|
|
36
|
-
--color-muted: var(--muted);
|
|
37
|
-
--color-secondary-foreground: var(--secondary-foreground);
|
|
38
|
-
--color-secondary: var(--secondary);
|
|
39
|
-
--color-primary-foreground: var(--primary-foreground);
|
|
40
|
-
--color-primary: var(--primary);
|
|
41
|
-
--color-popover-foreground: var(--popover-foreground);
|
|
42
|
-
--color-popover: var(--popover);
|
|
43
|
-
--color-card-foreground: var(--card-foreground);
|
|
44
|
-
--color-card: var(--card);
|
|
45
|
-
--color-foreground: var(--foreground);
|
|
46
|
-
--color-background: var(--background);
|
|
47
|
-
--radius-sm: calc(var(--radius) * 0.6);
|
|
48
|
-
--radius-md: calc(var(--radius) * 0.8);
|
|
49
|
-
--radius-lg: var(--radius);
|
|
50
|
-
--radius-xl: calc(var(--radius) * 1.4);
|
|
51
|
-
--radius-2xl: calc(var(--radius) * 1.8);
|
|
52
|
-
--radius-3xl: calc(var(--radius) * 2.2);
|
|
53
|
-
--radius-4xl: calc(var(--radius) * 2.6);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
:root {
|
|
57
|
-
--background: oklch(1 0 0);
|
|
58
|
-
--foreground: oklch(0.153 0.006 107.1);
|
|
59
|
-
--card: oklch(1 0 0);
|
|
60
|
-
--card-foreground: oklch(0.153 0.006 107.1);
|
|
61
|
-
--popover: oklch(1 0 0);
|
|
62
|
-
--popover-foreground: oklch(0.153 0.006 107.1);
|
|
63
|
-
--primary: oklch(0.511 0.096 186.391);
|
|
64
|
-
--primary-foreground: oklch(0.984 0.014 180.72);
|
|
65
|
-
--secondary: oklch(0.967 0.001 286.375);
|
|
66
|
-
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
67
|
-
--muted: oklch(0.966 0.005 106.5);
|
|
68
|
-
--muted-foreground: oklch(0.58 0.031 107.3);
|
|
69
|
-
--accent: oklch(0.966 0.005 106.5);
|
|
70
|
-
--accent-foreground: oklch(0.228 0.013 107.4);
|
|
71
|
-
--destructive: oklch(0.577 0.245 27.325);
|
|
72
|
-
--border: oklch(0.93 0.007 106.5);
|
|
73
|
-
--input: oklch(0.93 0.007 106.5);
|
|
74
|
-
--ring: oklch(0.737 0.021 106.9);
|
|
75
|
-
--chart-1: oklch(0.855 0.138 181.071);
|
|
76
|
-
--chart-2: oklch(0.704 0.14 182.503);
|
|
77
|
-
--chart-3: oklch(0.6 0.118 184.704);
|
|
78
|
-
--chart-4: oklch(0.511 0.096 186.391);
|
|
79
|
-
--chart-5: oklch(0.437 0.078 188.216);
|
|
80
|
-
--radius: 0.625rem;
|
|
81
|
-
--sidebar: oklch(0.988 0.003 106.5);
|
|
82
|
-
--sidebar-foreground: oklch(0.153 0.006 107.1);
|
|
83
|
-
--sidebar-primary: oklch(0.6 0.118 184.704);
|
|
84
|
-
--sidebar-primary-foreground: oklch(0.984 0.014 180.72);
|
|
85
|
-
--sidebar-accent: oklch(0.966 0.005 106.5);
|
|
86
|
-
--sidebar-accent-foreground: oklch(0.228 0.013 107.4);
|
|
87
|
-
--sidebar-border: oklch(0.93 0.007 106.5);
|
|
88
|
-
--sidebar-ring: oklch(0.737 0.021 106.9);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.dark {
|
|
92
|
-
--background: #101112;
|
|
93
|
-
--foreground: #F1F2F3;
|
|
94
|
-
--card: #17181A;
|
|
95
|
-
--card-foreground: #F1F2F3;
|
|
96
|
-
--popover: #17181A;
|
|
97
|
-
--popover-foreground: #F1F2F3;
|
|
98
|
-
--primary: #266DF0;
|
|
99
|
-
--primary-foreground: #FFFFFF;
|
|
100
|
-
--secondary: rgb(255 255 255 / 0.05);
|
|
101
|
-
--secondary-foreground: #F1F2F3;
|
|
102
|
-
--muted: rgb(255 255 255 / 0.05);
|
|
103
|
-
--muted-foreground: rgb(255 255 255 / 0.6);
|
|
104
|
-
--accent: rgb(255 255 255 / 0.05);
|
|
105
|
-
--accent-foreground: #F1F2F3;
|
|
106
|
-
--destructive: oklch(0.68 0.19 25);
|
|
107
|
-
--border: #1B1C1F;
|
|
108
|
-
--input: #212224;
|
|
109
|
-
--ring: #266DF0;
|
|
110
|
-
--chart-1: #266DF0;
|
|
111
|
-
--chart-2: #4E8CFC;
|
|
112
|
-
--chart-3: #9B69FF;
|
|
113
|
-
--chart-4: #6EA5F7;
|
|
114
|
-
--chart-5: #2D3A55;
|
|
115
|
-
--sidebar: #101112;
|
|
116
|
-
--sidebar-foreground: #F1F2F3;
|
|
117
|
-
--sidebar-primary: #266DF0;
|
|
118
|
-
--sidebar-primary-foreground: #FFFFFF;
|
|
119
|
-
--sidebar-accent: rgb(255 255 255 / 0.05);
|
|
120
|
-
--sidebar-accent-foreground: #F1F2F3;
|
|
121
|
-
--sidebar-border: #1B1C1F;
|
|
122
|
-
--sidebar-ring: #266DF0;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
16
|
@layer base {
|
|
126
17
|
* {
|
|
127
18
|
@apply border-border outline-ring/50;
|
|
128
19
|
}
|
|
129
20
|
body {
|
|
130
|
-
@apply bg-background text-foreground;
|
|
21
|
+
@apply bg-background font-sans text-body text-foreground;
|
|
22
|
+
-webkit-font-smoothing: antialiased;
|
|
131
23
|
}
|
|
132
24
|
button:not(:disabled), [role="button"]:not(:disabled) {
|
|
133
25
|
cursor: pointer;
|
|
134
26
|
}
|
|
135
|
-
html {
|
|
136
|
-
@apply font-sans;
|
|
137
|
-
}
|
|
138
27
|
}
|
package/template/src/main.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { RouterProvider, createRouter } from "@tanstack/react-router";
|
|
|
4
4
|
import { MontyProvider, MontySplash } from "@montytools/sdk/react";
|
|
5
5
|
|
|
6
6
|
import "./index.css";
|
|
7
|
-
import { app } from "
|
|
7
|
+
import { app } from "./monty.gen";
|
|
8
8
|
import { routeTree } from "./routeTree.gen";
|
|
9
9
|
|
|
10
10
|
// defaultPendingComponent: lazy route chunks show the same splash the rest
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineApp } from "@montytools/sdk";
|
|
2
|
+
|
|
3
|
+
// GENERATED by Monty from the app's config stored in the workspace.
|
|
4
|
+
// DO NOT EDIT: edits here never land anywhere, and this file is
|
|
5
|
+
// overwritten on every sync (a running `monty dev` regenerates it within
|
|
6
|
+
// a heartbeat of a remote change; `monty save` and `monty schema pull`
|
|
7
|
+
// refresh it too). Change the app through the doors instead:
|
|
8
|
+
// `monty schema set`, the MCP schema_update tool, or Configuration.
|
|
9
|
+
// Import { app } from it for typed SDK hooks — that part is yours.
|
|
10
|
+
export const app = defineApp({
|
|
11
|
+
slug: "new-app",
|
|
12
|
+
name: "New app",
|
|
13
|
+
icon: "layout-grid",
|
|
14
|
+
tables: {},
|
|
15
|
+
});
|
|
16
|
+
export type App = typeof app;
|