create-ramonyx-app 0.7.0 → 1.1.0

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Scaffolder aplikasi Ramonyx — SSR React + hydration, file routing `src/routes`.
4
4
 
5
- > **Alpha (0.7.0)** — butuh **Node.js ≥ 24**.
5
+ > **Alpha (1.1.0)** — butuh **Node.js ≥ 24**.
6
6
 
7
7
  ## Pemakaian
8
8
 
package/dist/cli.js CHANGED
@@ -4,7 +4,9 @@
4
4
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- var templateDir = join(dirname(fileURLToPath(import.meta.url)), "..", "template");
7
+ var packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
8
+ var templateDir = join(packageRoot, "template");
9
+ var agentTemplateDir = join(packageRoot, "template-agent");
8
10
  var USAGE = `create-ramonyx-app \u2014 buat aplikasi Ramonyx baru
9
11
 
10
12
  Penggunaan:
@@ -13,8 +15,9 @@ Penggunaan:
13
15
  pnpm create:app <dir> # di dalam repo (workspace)
14
16
 
15
17
  Opsi:
16
- --force, -f Timpa berkas template walau direktori tujuan tidak kosong
17
- --help, -h Tampilkan bantuan ini
18
+ --force, -f Timpa berkas template walau direktori tujuan tidak kosong
19
+ --with-agent-docs Sertakan AGENTS.md + skill agent opt-in (tidak default)
20
+ --help, -h Tampilkan bantuan ini
18
21
  `;
19
22
  function usage(exitCode) {
20
23
  const write = exitCode === 0 ? console.log : console.error;
@@ -23,10 +26,13 @@ function usage(exitCode) {
23
26
  }
24
27
  var args = process.argv.slice(2);
25
28
  var force = false;
29
+ var withAgentDocs = false;
26
30
  var target;
27
31
  for (const arg of args) {
28
32
  if (arg === "--force" || arg === "-f") {
29
33
  force = true;
34
+ } else if (arg === "--with-agent-docs") {
35
+ withAgentDocs = true;
30
36
  } else if (arg === "--help" || arg === "-h") {
31
37
  usage(0);
32
38
  } else if (arg.startsWith("-")) {
@@ -71,6 +77,9 @@ function copy(source, destination) {
71
77
  writeFileSync(join(dirname(destination), name), text);
72
78
  }
73
79
  copy(templateDir, targetDir);
80
+ if (withAgentDocs) {
81
+ copy(agentTemplateDir, targetDir);
82
+ }
74
83
  console.log(`\u2713 App "${appName}" dibuat di ${targetDir}
75
84
 
76
85
  Langkah berikutnya:
@@ -78,4 +87,4 @@ Langkah berikutnya:
78
87
  cd ${target}
79
88
  pnpm install # atau npm install
80
89
  pnpm start # http://127.0.0.1:3000
81
- `);
90
+ ${withAgentDocs ? "\nAgent docs: AGENTS.md + .opencode/skills/ramonyx/SKILL.md sudah disertakan.\n" : ""}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ramonyx-app",
3
- "version": "0.7.0",
3
+ "version": "1.1.0",
4
4
  "description": "Scaffolder aplikasi Ramonyx (SSR React + hydration, file routing src/routes).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "template",
13
+ "template-agent",
13
14
  "README.md",
14
15
  "LICENSE"
15
16
  ],
@@ -7,13 +7,16 @@ Aplikasi [Ramonyx](https://github.com/ramonyx/ramonyx) — framework full-stack
7
7
  ## Menjalankan
8
8
 
9
9
  ```bash
10
- pnpm install
10
+ pnpm install # atau: bun install / npm install
11
11
  pnpm dev # Vite + HMR (http://localhost:5173) → SSR :3000
12
12
  pnpm build # bundel klien (wajib sebelum start produksi)
13
13
  pnpm start # http://127.0.0.1:3000 (NODE_ENV=production otomatis)
14
14
  ```
15
15
 
16
- Perintah memakai CLI **`ramonyx`** (`@ramonyx/devtools`): `ramonyx dev|build|start|doctor`.
16
+ Dengan Bun: `bun run dev`, `bun run build`, `bun run start`. Perintah memakai CLI **`ramonyx`**
17
+ (`@ramonyx/devtools`) yang terpasang **lokal** di `node_modules/.bin`. Untuk memanggil CLI di luar
18
+ package script, gunakan runner package manager: `bunx ramonyx …`, `pnpm exec ramonyx …`, atau
19
+ `npx ramonyx …`. `ramonyx …` tanpa runner hanya tersedia setelah instalasi global.
17
20
 
18
21
  ## Struktur
19
22
 
@@ -11,20 +11,23 @@
11
11
  "dev": "ramonyx dev"
12
12
  },
13
13
  "dependencies": {
14
- "@ramonyx/adapter-runtime": "^0.7.0",
15
- "@ramonyx/bundler": "^0.7.0",
16
- "@ramonyx/compiler": "^0.7.0",
17
- "@ramonyx/devtools": "^0.7.0",
18
- "@ramonyx/react": "^0.7.0",
19
- "@ramonyx/security": "^0.7.0",
20
- "@ramonyx/server": "^0.7.0",
21
- "@ramonyx/ssr": "^0.7.0",
22
- "ramonyx": "^0.7.0",
14
+ "@ramonyx/adapter-runtime": "^1.1.0",
15
+ "@ramonyx/bundler": "^1.1.0",
16
+ "@ramonyx/compiler": "^1.1.0",
17
+ "@ramonyx/devtools": "^1.1.0",
18
+ "@ramonyx/react": "^1.1.0",
19
+ "@ramonyx/security": "^1.1.0",
20
+ "@ramonyx/server": "^1.1.0",
21
+ "@ramonyx/ssr": "^1.1.0",
22
+ "ramonyx": "^1.1.0",
23
23
  "react": "19.3.1",
24
24
  "react-dom": "19.3.1"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/react": "19.3.0",
28
28
  "@types/react-dom": "19.3.0"
29
+ },
30
+ "imports": {
31
+ "#routes": "./.ramonyx/routes.ts"
29
32
  }
30
33
  }
@@ -0,0 +1,3 @@
1
+ /** Ikon data-URI agar aplikasi tidak meminta /favicon.ico dan mencatat 404. */
2
+ export const FAVICON =
3
+ "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%23151811'/%3E%3Cpath d='M16 5l11 11-11 11L5 16z' fill='%23233fce'/%3E%3C/svg%3E";
@@ -1,18 +1,27 @@
1
1
  import type { PageProps } from "@ramonyx/react";
2
2
 
3
- export const metadata = { title: "Tidak ditemukan", status: 404 };
3
+ import { FAVICON } from "../../favicon.ts";
4
+
5
+ export const metadata = {
6
+ title: "Tidak ditemukan",
7
+ status: 404,
8
+ head: `<link rel="icon" href="${FAVICON}" type="image/svg+xml" />`,
9
+ };
4
10
  /** Halaman 404 catch-all — tidak diprerender (rutenya tak terbatas). */
5
11
  export const prerender = false;
6
12
 
7
13
  export default function TidakDitemukan({ params }: PageProps<unknown>) {
8
14
  return (
9
- <main>
10
- <h1>404 — Tidak ditemukan</h1>
15
+ <main className="rw-main rw-notfound">
16
+ <p className="rw-kicker">404 · route not found</p>
17
+ <h1>Halaman tidak ditemukan.</h1>
11
18
  <p>
12
- Halaman <code>/{params.slug}</code> tidak ada.
19
+ Tidak ada rute untuk <code>/{params.slug}</code>.
13
20
  </p>
14
21
  <p>
15
- <a href="/">Kembali ke beranda</a>
22
+ <a className="rw-button" href="/">
23
+ Kembali ke beranda
24
+ </a>
16
25
  </p>
17
26
  </main>
18
27
  );
@@ -1,15 +1,36 @@
1
1
  import type { LayoutProps } from "@ramonyx/react";
2
2
 
3
+ import { WELCOME_CSS } from "../welcome-css.ts";
4
+
3
5
  export default function RootLayout({ children }: LayoutProps) {
4
6
  return (
5
- <div className="shell">
6
- <header className="topbar">
7
- <strong>__APP_NAME__</strong>
8
- <span>Ramonyx</span>
7
+ <div className="rw-shell">
8
+ <style>{WELCOME_CSS}</style>
9
+ <header className="rw-top">
10
+ <a className="rw-brand" href=".">
11
+ <span className="rw-mark" aria-hidden="true" />
12
+ __APP_NAME__
13
+ <small>ramonyx</small>
14
+ </a>
15
+ <nav className="rw-nav" aria-label="Project links">
16
+ <a href="/api/halo/dunia">API</a>
17
+ <a href="https://ramonyx.devmoon.net/docs/for-agents">For agents</a>
18
+ <a href="https://github.com/RamonyxJS/ramonyx">GitHub</a>
19
+ <a className="rw-nav-strong" href="https://ramonyx.devmoon.net/docs/getting-started">
20
+ Docs
21
+ </a>
22
+ </nav>
9
23
  </header>
10
24
  {children}
11
- <footer className="kaki">
12
- Dibuat dengan Ramonyx — <code>src/routes</code> berisi halaman & API.
25
+ <footer className="rw-footer">
26
+ <span>
27
+ Built with <a href="https://ramonyx.devmoon.net/">Ramonyx</a> · MIT
28
+ </span>
29
+ <nav aria-label="Footer">
30
+ <a href="https://ramonyx.devmoon.net/docs/cli">CLI</a>
31
+ <a href="https://ramonyx.devmoon.net/docs/deployment">Deploy</a>
32
+ <a href="https://ramonyx.devmoon.net/docs/troubleshooting">Troubleshooting</a>
33
+ </nav>
13
34
  </footer>
14
35
  </div>
15
36
  );
@@ -3,17 +3,19 @@ import { Image } from "@ramonyx/react/assets";
3
3
  import type { PageActionContext } from "ramonyx";
4
4
  import { useState } from "react";
5
5
 
6
+ import { FAVICON } from "../favicon.ts";
7
+
6
8
  export const metadata = {
7
9
  title: "__APP_NAME__",
8
- description: "Aplikasi Ramonyx — SSR React, file routing, dan server actions.",
10
+ description: "A Ramonyx app with SSR React, file routing, server actions, and secure defaults.",
11
+ head: `<link rel="icon" href="${FAVICON}" type="image/svg+xml" />`,
9
12
  };
13
+
10
14
  /** Cache data loader 60 dtk (ADR-0066) — hapus atau sesuaikan bila data harus selalu segar. */
11
15
  export const revalidate = 60;
12
16
 
13
17
  export async function loader({ assets }: { assets?: { url(path: string): string | undefined } }) {
14
18
  return {
15
- pesan: "Halo dari SSR Ramonyx",
16
- fitur: ["SSR React", "hydration", "route API"],
17
19
  logo: assets?.url("logo.svg"),
18
20
  };
19
21
  }
@@ -22,50 +24,186 @@ export async function loader({ assets }: { assets?: { url(path: string): string
22
24
  export function action({ formData }: PageActionContext) {
23
25
  const nama = String(formData.get("nama") ?? "").trim();
24
26
  if (nama.length < 2) {
25
- return { errors: { nama: "Nama minimal 2 karakter" } };
27
+ return { errors: { nama: "Enter at least two characters." } };
26
28
  }
27
- return { message: `Halo, ${nama}!` };
29
+ return { message: `Hello, ${nama}!` };
28
30
  }
29
31
 
30
32
  /** Komponen interaktif: server merender 0, klik menambah setelah hydration. */
31
33
  function Counter() {
32
34
  const [hitungan, setHitungan] = useState(0);
33
35
  return (
34
- <p>
35
- <button type="button" onClick={() => setHitungan((nilai) => nilai + 1)}>
36
- Klik saya: {hitungan}
37
- </button>
38
- </p>
36
+ <button type="button" className="rw-counter" onClick={() => setHitungan((nilai) => nilai + 1)}>
37
+ Clicks: <strong>{hitungan}</strong>
38
+ </button>
39
39
  );
40
40
  }
41
41
 
42
- export default function Beranda({
43
- data,
44
- action,
45
- }: PageProps<{ pesan: string; fitur: string[]; logo?: string }>) {
42
+ export default function Welcome({ data, action }: PageProps<{ logo?: string }>) {
46
43
  return (
47
- <main>
48
- <h1>
49
- <Image src={data.logo} alt="Logo" width={32} height={32} /> {data.pesan}
50
- </h1>
51
- <ul>
52
- {data.fitur.map((item) => (
53
- <li key={item}>{item}</li>
54
- ))}
55
- </ul>
56
- <Counter />
57
- <h2>Form dengan action</h2>
58
- <form method="post">
59
- <label>
60
- Nama: <input name="nama" />
61
- </label>
62
- <button type="submit">Kirim</button>
63
- </form>
64
- {action?.errors?.nama !== undefined ? <p role="alert">{action.errors.nama}</p> : null}
65
- {action?.message !== undefined ? <p role="status">{action.message}</p> : null}
66
- <p>
67
- Coba API dinamis: <a href="/api/halo/dunia">/api/halo/dunia</a>
68
- </p>
44
+ <main className="rw-main">
45
+ <section className="rw-hero">
46
+ <div className="rw-hero-copy">
47
+ <p className="rw-kicker">
48
+ <span className="rw-dot" aria-hidden="true" />
49
+ Development server is running
50
+ </p>
51
+ <h1>
52
+ Your app is live.
53
+ <br />
54
+ <span>Now make it yours.</span>
55
+ </h1>
56
+ <p className="rw-lede">
57
+ Ramonyx is a full-stack TypeScript framework with file routing, SSR React, server
58
+ actions, and secure defaults. Edit one file and the browser updates; ship the same route
59
+ tree to Node, static hosting, or the edge.
60
+ </p>
61
+ <div className="rw-actions">
62
+ <a className="rw-button" href="#build">
63
+ Start building <span aria-hidden="true">↓</span>
64
+ </a>
65
+ <a
66
+ className="rw-quiet"
67
+ href="https://ramonyx.devmoon.net/docs/getting-started"
68
+ target="_blank"
69
+ rel="noreferrer"
70
+ >
71
+ Read the docs <span aria-hidden="true">↗</span>
72
+ </a>
73
+ </div>
74
+ <ul className="rw-stack" aria-label="Stack">
75
+ <li>Node 24+</li>
76
+ <li>React 19</li>
77
+ <li>TypeScript</li>
78
+ <li>Edge-ready</li>
79
+ </ul>
80
+ </div>
81
+
82
+ <aside className="rw-status" aria-label="Project status">
83
+ <div className="rw-status-head">
84
+ <span>APP STATUS</span>
85
+ <span className="rw-live">ready</span>
86
+ </div>
87
+ <p className="rw-url">localhost:5173</p>
88
+ <p className="rw-status-sub">SSR + hydration · Vite HMR</p>
89
+ <div className="rw-brandline">
90
+ <Image src={data.logo} alt="__APP_NAME__ logo" width={25} height={25} />
91
+ <span>__APP_NAME__</span>
92
+ </div>
93
+ <pre className="rw-tree">{`src/routes/
94
+ ├─ page.tsx /
95
+ ├─ layout.tsx shell
96
+ ├─ api/halo/[nama].ts GET /api/halo/:nama
97
+ └─ [...slug]/page.tsx 404`}</pre>
98
+ <div className="rw-status-foot">
99
+ <span>secure defaults on</span>
100
+ <span>1 file changed → HMR</span>
101
+ </div>
102
+ </aside>
103
+ </section>
104
+
105
+ <section className="rw-section" id="build">
106
+ <div className="rw-section-head">
107
+ <span className="rw-index">01</span>
108
+ <h2>Start here</h2>
109
+ <p>Three small edits take this from a welcome screen to your application.</p>
110
+ </div>
111
+ <div className="rw-steps">
112
+ <article className="rw-step">
113
+ <span className="rw-step-index">01</span>
114
+ <h3>Make this page yours</h3>
115
+ <p>
116
+ This route is a normal React component with a server loader. Change the copy first.
117
+ </p>
118
+ <code>src/routes/page.tsx</code>
119
+ </article>
120
+ <article className="rw-step">
121
+ <span className="rw-step-index">02</span>
122
+ <h3>Add a route</h3>
123
+ <p>Create a folder and a page file. The filename becomes the URL; no route config.</p>
124
+ <a href="https://ramonyx.devmoon.net/docs/routing">Open routing docs →</a>
125
+ </article>
126
+ <article className="rw-step">
127
+ <span className="rw-step-index">03</span>
128
+ <h3>Ship it</h3>
129
+ <p>
130
+ Build for Node, export static pages, or bundle the same app as an edge fetch handler.
131
+ </p>
132
+ <a href="https://ramonyx.devmoon.net/docs/deployment">See deploy targets →</a>
133
+ </article>
134
+ </div>
135
+ </section>
136
+
137
+ <section className="rw-section" aria-labelledby="rw-try-title">
138
+ <div className="rw-section-head">
139
+ <span className="rw-index">02</span>
140
+ <h2 id="rw-try-title">Try the framework</h2>
141
+ <p>Both examples use the real runtime: hydration and a POST action, not a mock.</p>
142
+ </div>
143
+ <div className="rw-demo">
144
+ <div className="rw-demo-panel">
145
+ <span className="rw-label">HYDRATION</span>
146
+ <h2>Server first. Then interactive.</h2>
147
+ <p>
148
+ The button arrives as static HTML with zero clicks. React hydrates it in place, so the
149
+ first click is instant and the page stays useful without JavaScript.
150
+ </p>
151
+ <Counter />
152
+ </div>
153
+ <div className="rw-demo-panel">
154
+ <span className="rw-label">SERVER ACTION</span>
155
+ <h2>A form without a client API.</h2>
156
+ <p>
157
+ This form posts to the same route. Validation and mutation run on the server, then the
158
+ page re-renders. Disable JavaScript and it still works.
159
+ </p>
160
+ <form method="post" className="rw-form">
161
+ <label htmlFor="nama">Your name</label>
162
+ <div className="rw-form-row">
163
+ <input id="nama" name="nama" placeholder="Rina" autoComplete="name" />
164
+ <button type="submit">Send</button>
165
+ </div>
166
+ </form>
167
+ {action?.errors?.nama !== undefined ? (
168
+ <p className="rw-error" role="alert">
169
+ {action.errors.nama}
170
+ </p>
171
+ ) : null}
172
+ {action?.message !== undefined ? (
173
+ <p className="rw-success" role="status">
174
+ {action.message}
175
+ </p>
176
+ ) : null}
177
+ <p className="rw-api-link">
178
+ API route: <a href="/api/halo/dunia">/api/halo/dunia</a>
179
+ </p>
180
+ </div>
181
+ </div>
182
+ </section>
183
+
184
+ <section className="rw-features" aria-label="Framework features">
185
+ <article className="rw-feature">
186
+ <h3>File routing</h3>
187
+ <p>Folders become URLs. Layouts, error boundaries, and API methods live beside pages.</p>
188
+ </article>
189
+ <article className="rw-feature">
190
+ <h3>SSR + streaming</h3>
191
+ <p>
192
+ Render React on the server, hydrate only what the page needs, and opt into streaming.
193
+ </p>
194
+ </article>
195
+ <article className="rw-feature">
196
+ <h3>Typed actions</h3>
197
+ <p>Forms validate at the boundary with Standard Schema and work without JavaScript.</p>
198
+ </article>
199
+ <article className="rw-feature">
200
+ <h3>Secure by default</h3>
201
+ <p>
202
+ CSP nonces, CSRF, safe cookies, body limits, and SSRF guards are available in the
203
+ framework.
204
+ </p>
205
+ </article>
206
+ </section>
69
207
  </main>
70
208
  );
71
209
  }
@@ -0,0 +1,448 @@
1
+ // Gaya halaman sambutan template. Sengaja berupa string agar SSR dan bundle klien memakai
2
+ // stylesheet yang sama tanpa bergantung pada konfigurasi CSS global.
3
+ export const WELCOME_CSS = `
4
+ :root {
5
+ color-scheme: light dark;
6
+ --rw-bg: #f3f1e8;
7
+ --rw-surface: #fbfaf5;
8
+ --rw-ink: #14170f;
9
+ --rw-muted: #626858;
10
+ --rw-line: #d5d2c2;
11
+ --rw-line-strong: #b9b5a2;
12
+ --rw-blue: #233fce;
13
+ --rw-blue-soft: #e4e7fb;
14
+ --rw-orange: #d95a2b;
15
+ --rw-dark: #151811;
16
+ --rw-dark-2: #1c2117;
17
+ --rw-dark-fg: #edf0e2;
18
+ --rw-dark-muted: #a7ae9a;
19
+ --rw-dark-line: #30362a;
20
+ --rw-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, monospace;
21
+ --rw-sans: "Avenir Next", "Segoe UI", ui-sans-serif, system-ui, sans-serif;
22
+ --rw-serif: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, Georgia, serif;
23
+ --rw-max: 1180px;
24
+ --rw-radius: 4px;
25
+ }
26
+
27
+ html { scroll-behavior: smooth; }
28
+ * { box-sizing: border-box; }
29
+ body {
30
+ margin: 0;
31
+ background: var(--rw-bg);
32
+ color: var(--rw-ink);
33
+ font: 400 16px/1.6 var(--rw-sans);
34
+ -webkit-font-smoothing: antialiased;
35
+ text-rendering: optimizeLegibility;
36
+ }
37
+ ::selection { background: var(--rw-blue); color: #fff; }
38
+ a { color: inherit; text-decoration-color: color-mix(in srgb, var(--rw-blue) 45%, transparent); text-underline-offset: 3px; }
39
+ a:hover { text-decoration-color: var(--rw-blue); }
40
+ :focus-visible { outline: 2px solid var(--rw-blue); outline-offset: 3px; }
41
+ code, pre { font-family: var(--rw-mono); }
42
+ button, input { font: inherit; }
43
+
44
+ .rw-shell {
45
+ min-height: 100vh;
46
+ display: flex;
47
+ flex-direction: column;
48
+ overflow-x: clip;
49
+ background-image: linear-gradient(90deg, transparent 0, transparent calc(100% - 1px), var(--rw-line) calc(100% - 1px));
50
+ background-size: 25% 100%;
51
+ background-repeat: repeat-x;
52
+ }
53
+
54
+ .rw-main { width: min(100% - 48px, var(--rw-max)); margin: 0 auto; flex: 1; }
55
+
56
+ .rw-top {
57
+ width: min(100% - 48px, var(--rw-max));
58
+ margin: 0 auto;
59
+ min-height: 72px;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 28px;
63
+ border-bottom: 1px solid var(--rw-line);
64
+ }
65
+ .rw-brand {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ gap: 11px;
69
+ text-decoration: none;
70
+ font-family: var(--rw-serif);
71
+ font-size: 25px;
72
+ line-height: 1;
73
+ letter-spacing: -0.015em;
74
+ }
75
+ .rw-mark {
76
+ width: 12px;
77
+ height: 12px;
78
+ background: var(--rw-blue);
79
+ transform: rotate(45deg);
80
+ flex: 0 0 auto;
81
+ }
82
+ .rw-brand small {
83
+ font: 500 10px/1 var(--rw-mono);
84
+ letter-spacing: 0.08em;
85
+ color: var(--rw-muted);
86
+ border-left: 1px solid var(--rw-line-strong);
87
+ padding-left: 11px;
88
+ }
89
+ .rw-nav { display: flex; align-items: center; gap: 20px; margin-left: auto; }
90
+ .rw-nav a {
91
+ text-decoration: none;
92
+ color: var(--rw-muted);
93
+ font-size: 14px;
94
+ padding: 5px 0;
95
+ border-bottom: 1px solid transparent;
96
+ }
97
+ .rw-nav a:hover { color: var(--rw-ink); border-bottom-color: var(--rw-blue); }
98
+ .rw-nav .rw-nav-strong {
99
+ color: var(--rw-ink);
100
+ border: 1px solid var(--rw-line-strong);
101
+ border-radius: var(--rw-radius);
102
+ padding: 7px 12px;
103
+ background: var(--rw-surface);
104
+ }
105
+ .rw-nav .rw-nav-strong:hover { border-color: var(--rw-ink); }
106
+
107
+ .rw-hero {
108
+ display: grid;
109
+ grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr);
110
+ gap: 64px;
111
+ align-items: center;
112
+ padding: 84px 0 78px;
113
+ border-bottom: 1px solid var(--rw-line);
114
+ }
115
+ .rw-hero-copy { max-width: 650px; }
116
+ .rw-kicker {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 9px;
120
+ margin: 0 0 26px;
121
+ color: var(--rw-muted);
122
+ font: 500 12px/1.4 var(--rw-mono);
123
+ }
124
+ .rw-dot {
125
+ width: 7px;
126
+ height: 7px;
127
+ border-radius: 50%;
128
+ background: var(--rw-orange);
129
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--rw-orange) 16%, transparent);
130
+ flex: 0 0 auto;
131
+ }
132
+ .rw-hero h1 {
133
+ margin: 0 0 24px;
134
+ font-family: var(--rw-serif);
135
+ font-weight: 400;
136
+ font-size: clamp(52px, 7.2vw, 94px);
137
+ line-height: 0.94;
138
+ letter-spacing: -0.035em;
139
+ text-wrap: balance;
140
+ }
141
+ .rw-hero h1 span { color: var(--rw-blue); }
142
+ .rw-lede {
143
+ max-width: 48ch;
144
+ margin: 0 0 30px;
145
+ color: var(--rw-muted);
146
+ font-size: clamp(17px, 1.5vw, 19px);
147
+ line-height: 1.55;
148
+ }
149
+ .rw-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px 22px; }
150
+ .rw-button, .rw-quiet {
151
+ display: inline-flex;
152
+ align-items: center;
153
+ gap: 9px;
154
+ text-decoration: none;
155
+ font-size: 14px;
156
+ font-weight: 600;
157
+ border-radius: var(--rw-radius);
158
+ }
159
+ .rw-button {
160
+ min-height: 43px;
161
+ padding: 0 17px;
162
+ background: var(--rw-blue);
163
+ color: #fff;
164
+ border: 1px solid var(--rw-blue);
165
+ }
166
+ .rw-button:hover { background: color-mix(in srgb, var(--rw-blue) 84%, #000); }
167
+ .rw-quiet { color: var(--rw-ink); padding: 8px 0; border-bottom: 1px solid var(--rw-line-strong); border-radius: 0; }
168
+ .rw-quiet:hover { border-bottom-color: var(--rw-blue); }
169
+ .rw-stack {
170
+ display: flex;
171
+ flex-wrap: wrap;
172
+ gap: 8px 20px;
173
+ margin: 38px 0 0;
174
+ padding: 0;
175
+ list-style: none;
176
+ color: var(--rw-muted);
177
+ font: 12px/1.4 var(--rw-mono);
178
+ }
179
+ .rw-stack li::before { content: "/"; color: var(--rw-line-strong); margin-right: 7px; }
180
+
181
+ .rw-status {
182
+ position: relative;
183
+ background: var(--rw-dark);
184
+ color: var(--rw-dark-fg);
185
+ border: 1px solid var(--rw-dark-line);
186
+ border-radius: var(--rw-radius);
187
+ padding: 19px;
188
+ box-shadow: 12px 12px 0 var(--rw-blue-soft);
189
+ }
190
+ .rw-status::before {
191
+ content: "";
192
+ position: absolute;
193
+ inset: 0;
194
+ pointer-events: none;
195
+ background: linear-gradient(135deg, transparent 0 78%, rgba(255,255,255,.035) 78%);
196
+ }
197
+ .rw-status-head {
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: space-between;
201
+ gap: 14px;
202
+ padding-bottom: 13px;
203
+ border-bottom: 1px solid var(--rw-dark-line);
204
+ font: 500 10px/1.4 var(--rw-mono);
205
+ letter-spacing: 0.09em;
206
+ color: var(--rw-dark-muted);
207
+ }
208
+ .rw-live { display: inline-flex; align-items: center; gap: 6px; color: #a8dfad; letter-spacing: 0; }
209
+ .rw-live::before {
210
+ content: "";
211
+ width: 6px;
212
+ height: 6px;
213
+ border-radius: 50%;
214
+ background: #7bdc85;
215
+ animation: rw-pulse 2.4s ease-out infinite;
216
+ }
217
+ .rw-url {
218
+ margin: 16px 0 3px;
219
+ font: 500 14px/1.4 var(--rw-mono);
220
+ color: #cbd6ff;
221
+ overflow-wrap: anywhere;
222
+ }
223
+ .rw-status-sub { margin: 0; color: var(--rw-dark-muted); font-size: 12px; }
224
+ .rw-status .rw-brandline {
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 10px;
228
+ margin: 21px 0 14px;
229
+ padding-top: 17px;
230
+ border-top: 1px solid var(--rw-dark-line);
231
+ font-family: var(--rw-serif);
232
+ font-size: 21px;
233
+ }
234
+ .rw-status .rw-brandline img { width: 25px; height: 25px; display: block; }
235
+ .rw-tree {
236
+ margin: 0;
237
+ padding: 15px;
238
+ background: rgba(0,0,0,.18);
239
+ border: 1px solid var(--rw-dark-line);
240
+ border-radius: 2px;
241
+ color: #c9d2bd;
242
+ font: 11.5px/1.75 var(--rw-mono);
243
+ overflow-x: auto;
244
+ }
245
+ .rw-tree b { color: #f0c6a9; font-weight: 500; }
246
+ .rw-tree em { color: #8fa5ff; font-style: normal; }
247
+ .rw-status-foot {
248
+ display: flex;
249
+ justify-content: space-between;
250
+ gap: 12px;
251
+ margin-top: 14px;
252
+ color: var(--rw-dark-muted);
253
+ font: 10px/1.4 var(--rw-mono);
254
+ }
255
+
256
+ .rw-section { padding: 68px 0 0; }
257
+ .rw-section-head {
258
+ display: grid;
259
+ grid-template-columns: 52px minmax(0, 1fr) minmax(220px, 0.55fr);
260
+ gap: 20px;
261
+ align-items: start;
262
+ padding-bottom: 23px;
263
+ border-bottom: 1px solid var(--rw-line);
264
+ }
265
+ .rw-index { color: var(--rw-orange); font: 500 12px/1.4 var(--rw-mono); padding-top: 8px; }
266
+ .rw-section h2 {
267
+ margin: 0;
268
+ font-family: var(--rw-serif);
269
+ font-size: clamp(30px, 3.6vw, 47px);
270
+ font-weight: 400;
271
+ line-height: 1;
272
+ letter-spacing: -0.025em;
273
+ }
274
+ .rw-section-head p { margin: 3px 0 0; color: var(--rw-muted); font-size: 14px; line-height: 1.5; }
275
+
276
+ .rw-steps {
277
+ display: grid;
278
+ grid-template-columns: repeat(3, minmax(0, 1fr));
279
+ border-bottom: 1px solid var(--rw-line);
280
+ }
281
+ .rw-step { padding: 25px 24px 28px 0; border-right: 1px solid var(--rw-line); }
282
+ .rw-step + .rw-step { padding-left: 24px; }
283
+ .rw-step:last-child { border-right: 0; }
284
+ .rw-step-index { color: var(--rw-muted); font: 500 11px/1.4 var(--rw-mono); }
285
+ .rw-step h3 { margin: 18px 0 7px; font-family: var(--rw-serif); font-size: 25px; font-weight: 400; line-height: 1.1; }
286
+ .rw-step p { margin: 0 0 16px; color: var(--rw-muted); font-size: 14px; line-height: 1.55; }
287
+ .rw-step code { display: inline-block; padding: 4px 7px; background: var(--rw-blue-soft); color: var(--rw-blue); border-radius: 2px; font-size: 11.5px; }
288
+ .rw-step a { color: var(--rw-blue); font-size: 13px; font-weight: 600; text-decoration: none; }
289
+ .rw-step a:hover { text-decoration: underline; }
290
+
291
+ .rw-demo {
292
+ display: grid;
293
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
294
+ gap: 0;
295
+ border-bottom: 1px solid var(--rw-line);
296
+ }
297
+ .rw-demo-panel { padding: 34px 32px 36px 0; }
298
+ .rw-demo-panel + .rw-demo-panel { padding-left: 32px; border-left: 1px solid var(--rw-line); }
299
+ .rw-label { color: var(--rw-orange); font: 500 10px/1.4 var(--rw-mono); letter-spacing: 0.08em; }
300
+ .rw-demo h2 { margin: 12px 0 9px; font-family: var(--rw-serif); font-size: clamp(27px, 3vw, 37px); font-weight: 400; line-height: 1.04; letter-spacing: -0.02em; }
301
+ .rw-demo p { max-width: 52ch; margin: 0 0 20px; color: var(--rw-muted); font-size: 14px; line-height: 1.55; }
302
+ .rw-counter {
303
+ display: inline-flex;
304
+ align-items: center;
305
+ gap: 10px;
306
+ border: 1px solid var(--rw-line-strong);
307
+ border-radius: var(--rw-radius);
308
+ background: var(--rw-surface);
309
+ color: var(--rw-ink);
310
+ padding: 10px 15px;
311
+ cursor: pointer;
312
+ font-weight: 600;
313
+ font-size: 14px;
314
+ }
315
+ .rw-counter:hover { border-color: var(--rw-blue); color: var(--rw-blue); }
316
+ .rw-counter strong { font-family: var(--rw-mono); font-size: 13px; color: var(--rw-blue); }
317
+ .rw-form { display: grid; gap: 9px; max-width: 410px; }
318
+ .rw-form label { color: var(--rw-muted); font: 11px/1.4 var(--rw-mono); }
319
+ .rw-form-row { display: flex; gap: 8px; }
320
+ .rw-form input {
321
+ width: 100%;
322
+ min-height: 42px;
323
+ padding: 9px 11px;
324
+ color: var(--rw-ink);
325
+ background: var(--rw-surface);
326
+ border: 1px solid var(--rw-line-strong);
327
+ border-radius: var(--rw-radius);
328
+ }
329
+ .rw-form input:focus { outline: 2px solid var(--rw-blue); outline-offset: 1px; }
330
+ .rw-form button {
331
+ flex: 0 0 auto;
332
+ min-height: 42px;
333
+ padding: 0 14px;
334
+ border: 1px solid var(--rw-ink);
335
+ border-radius: var(--rw-radius);
336
+ background: var(--rw-ink);
337
+ color: var(--rw-bg);
338
+ cursor: pointer;
339
+ font-weight: 600;
340
+ font-size: 13px;
341
+ }
342
+ .rw-form button:hover { background: var(--rw-blue); border-color: var(--rw-blue); color: #fff; }
343
+ .rw-error, .rw-success { margin: 10px 0 0; font-size: 13px; }
344
+ .rw-error { color: var(--rw-orange); }
345
+ .rw-success { color: var(--rw-blue); }
346
+ .rw-api-link { margin-top: 16px; font: 12px/1.5 var(--rw-mono); color: var(--rw-muted); }
347
+ .rw-api-link a { color: var(--rw-blue); }
348
+
349
+ .rw-features {
350
+ display: grid;
351
+ grid-template-columns: repeat(4, minmax(0, 1fr));
352
+ border-bottom: 1px solid var(--rw-line);
353
+ }
354
+ .rw-feature { min-height: 150px; padding: 24px 22px 27px 0; border-right: 1px solid var(--rw-line); }
355
+ .rw-feature + .rw-feature { padding-left: 22px; }
356
+ .rw-feature:last-child { border-right: 0; }
357
+ .rw-feature h3 { margin: 0 0 8px; font-family: var(--rw-serif); font-size: 21px; font-weight: 400; }
358
+ .rw-feature p { margin: 0; color: var(--rw-muted); font-size: 13px; line-height: 1.5; }
359
+
360
+ .rw-notfound {
361
+ max-width: 760px;
362
+ padding: 76px 0 90px;
363
+ }
364
+ .rw-notfound h1 {
365
+ margin: 0 0 16px;
366
+ font-family: var(--rw-serif);
367
+ font-size: clamp(42px, 6vw, 72px);
368
+ font-weight: 400;
369
+ line-height: 0.98;
370
+ letter-spacing: -0.03em;
371
+ }
372
+ .rw-notfound p { color: var(--rw-muted); }
373
+ .rw-notfound code { color: var(--rw-blue); font-size: 0.92em; }
374
+ .rw-notfound .rw-button { margin-top: 12px; color: #fff; }
375
+ .rw-footer {
376
+ width: min(100% - 48px, var(--rw-max));
377
+ margin: 0 auto;
378
+ padding: 23px 0 28px;
379
+ display: flex;
380
+ align-items: center;
381
+ justify-content: space-between;
382
+ gap: 20px;
383
+ color: var(--rw-muted);
384
+ font-size: 12px;
385
+ }
386
+ .rw-footer nav { display: flex; gap: 16px; }
387
+ .rw-footer a { text-decoration: none; color: var(--rw-ink); }
388
+ .rw-footer a:hover { color: var(--rw-blue); }
389
+
390
+ @keyframes rw-pulse {
391
+ 0% { box-shadow: 0 0 0 0 rgba(123,220,133,.45); }
392
+ 70% { box-shadow: 0 0 0 7px rgba(123,220,133,0); }
393
+ 100% { box-shadow: 0 0 0 0 rgba(123,220,133,0); }
394
+ }
395
+
396
+ @media (max-width: 940px) {
397
+ .rw-hero { grid-template-columns: 1fr; gap: 42px; padding: 60px 0 58px; }
398
+ .rw-status { max-width: 560px; }
399
+ .rw-section-head { grid-template-columns: 42px minmax(0, 1fr); }
400
+ .rw-section-head p { grid-column: 2; margin-top: 10px; }
401
+ .rw-features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
402
+ .rw-feature:nth-child(2) { border-right: 0; }
403
+ .rw-feature:nth-child(n+3) { border-top: 1px solid var(--rw-line); }
404
+ }
405
+
406
+ @media (max-width: 680px) {
407
+ .rw-main, .rw-top, .rw-footer { width: min(100% - 32px, var(--rw-max)); }
408
+ .rw-top { min-height: 62px; gap: 14px; }
409
+ .rw-brand { font-size: 22px; }
410
+ .rw-brand small { display: none; }
411
+ .rw-nav { gap: 12px; }
412
+ .rw-nav a:not(.rw-nav-strong) { display: none; }
413
+ .rw-hero h1 { font-size: clamp(47px, 14vw, 68px); }
414
+ .rw-status { padding: 16px; box-shadow: 7px 7px 0 var(--rw-blue-soft); }
415
+ .rw-steps, .rw-demo, .rw-features { grid-template-columns: 1fr; }
416
+ .rw-step, .rw-step + .rw-step, .rw-feature, .rw-feature + .rw-feature { padding: 22px 0; border-right: 0; border-left: 0; }
417
+ .rw-step + .rw-step, .rw-feature + .rw-feature, .rw-demo-panel + .rw-demo-panel { border-top: 1px solid var(--rw-line); }
418
+ .rw-demo-panel, .rw-demo-panel + .rw-demo-panel { padding: 28px 0; border-left: 0; }
419
+ .rw-section { padding-top: 50px; }
420
+ .rw-section-head { grid-template-columns: 32px minmax(0, 1fr); gap: 13px; }
421
+ .rw-form-row { flex-direction: column; }
422
+ .rw-footer { align-items: flex-start; flex-direction: column; gap: 10px; }
423
+ }
424
+
425
+ @media (prefers-color-scheme: dark) {
426
+ :root {
427
+ --rw-bg: #0d100b;
428
+ --rw-surface: #151a12;
429
+ --rw-ink: #edf0e2;
430
+ --rw-muted: #9ba38f;
431
+ --rw-line: #2a3024;
432
+ --rw-line-strong: #414a39;
433
+ --rw-blue: #aebaff;
434
+ --rw-blue-soft: #20263d;
435
+ --rw-orange: #f08a5e;
436
+ }
437
+ ::selection { color: #0d100b; }
438
+ .rw-button, .rw-notfound .rw-button { color: #12150e; }
439
+ .rw-button:hover { color: #fff; }
440
+ .rw-status { box-shadow: 12px 12px 0 #1b2238; }
441
+ .rw-status .rw-tree { background: rgba(0,0,0,.24); }
442
+ }
443
+
444
+ @media (prefers-reduced-motion: reduce) {
445
+ html { scroll-behavior: auto; }
446
+ .rw-live::before { animation: none; }
447
+ }
448
+ `;
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "jsx": "react-jsx",
8
+ "noEmit": true,
9
+ "skipLibCheck": true,
10
+ "allowImportingTsExtensions": true,
11
+ "paths": {
12
+ "#routes": ["./.ramonyx/routes.ts"]
13
+ }
14
+ },
15
+ "include": ["src", ".ramonyx"]
16
+ }
@@ -0,0 +1,18 @@
1
+ # Ramonyx skill
2
+
3
+ Use this skill when working in a Ramonyx application.
4
+
5
+ ## Workflow
6
+
7
+ 1. Read `AGENTS.md` and the relevant page module under `src/routes/`.
8
+ 2. Keep reads in `loader` and mutations in `action`; validate at trust boundaries.
9
+ 3. Run `pnpm typecheck` and the relevant tests before claiming a change works.
10
+ 4. For framework questions, query the Ramonyx docs (MCP `search_docs` / `get_page`) instead of
11
+ guessing APIs.
12
+
13
+ ## Safety
14
+
15
+ - Never put secrets in client components, loader payloads, or stateless signed cookies.
16
+ - Preserve CSRF and security-header middleware.
17
+ - Prefer the in-memory dev adapters only for local work; production multi-instance needs shared
18
+ session/rate-limit/cache adapters.
@@ -0,0 +1,34 @@
1
+ # Agent guide — Ramonyx app
2
+
3
+ This project uses Ramonyx (SSR React + file routing). Read the framework docs before changing
4
+ routing, data loading, auth, or security-sensitive code.
5
+
6
+ ## Commands
7
+
8
+ Use the package manager that installed the project (`pnpm`, `bun`, or `npm`):
9
+
10
+ - `pnpm dev` / `bun run dev` / `npm run dev` — Vite HMR + SSR on the port in `ramonyx.config.ts`.
11
+ - `pnpm build` / `bun run build` / `npm run build` — client bundle + server build.
12
+ - `pnpm start` / `bun run start` / `npm run start` — production server (`NODE_ENV=production`).
13
+ - `pnpm test` / `bun run test` / `npm test` — application tests.
14
+
15
+ The `ramonyx` binary is local to `node_modules/.bin`, not on the shell `PATH`. For one-off CLI
16
+ commands use `pnpm exec ramonyx …`, `bunx ramonyx …`, or `npx ramonyx …`.
17
+
18
+ ## Project map
19
+
20
+ - `src/app.ts` — `createApp()`, middleware, security headers.
21
+ - `src/routes/**` — file routes; `page.tsx` for pages, other files for API methods.
22
+ - `src/routes/**/middleware.ts` — per-folder middleware (root → leaf).
23
+ - `ramonyx.config.ts` — server/port and app options.
24
+
25
+ ## Rules
26
+
27
+ - Loaders/actions run on the server; never trust request JSON at a trust boundary — validate it.
28
+ - Mutations use a page `action` (works without JS); do not add a parallel fetch-only mutation path.
29
+ - Secrets come from env/bindings, never source or client bundles.
30
+ - Keep auth/session cookies `HttpOnly`, `Secure`, `SameSite=Lax` in production; use CSRF protection.
31
+ - Loader data must be JSON-serializable.
32
+ - Do not import server-only modules (`node:*`, database clients) into client components/islands.
33
+
34
+ For framework details, use the MCP docs server (`@ramonyx/mcp-docs-server`) or the docs site.