ruact 0.0.5 → 0.0.7

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +54 -2
  3. data/.rubocop_todo.yml +3 -115
  4. data/CHANGELOG.md +74 -18
  5. data/bench/server_functions_dispatch_bench.rb +109 -142
  6. data/bench/server_functions_dispatch_bench.results.md +29 -0
  7. data/docs/internal/decisions/server-functions-api.md +402 -0
  8. data/lib/generators/ruact/install/install_generator.rb +310 -25
  9. data/lib/generators/ruact/install/templates/Procfile.dev.tt +2 -0
  10. data/lib/generators/ruact/install/templates/dev.tt +16 -0
  11. data/lib/generators/ruact/install/templates/package.json.tt +17 -0
  12. data/lib/generators/ruact/install/templates/vite.config.js.tt +3 -1
  13. data/lib/generators/ruact/scaffold/scaffold_attribute.rb +114 -0
  14. data/lib/generators/ruact/scaffold/scaffold_form_helpers.rb +114 -0
  15. data/lib/generators/ruact/scaffold/scaffold_generator.rb +491 -0
  16. data/lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb +229 -0
  17. data/lib/generators/ruact/scaffold/templates/components/DeleteDialog.tsx.tt +104 -0
  18. data/lib/generators/ruact/scaffold/templates/components/Form.tsx.tt +212 -0
  19. data/lib/generators/ruact/scaffold/templates/components/List.tsx.tt +338 -0
  20. data/lib/generators/ruact/scaffold/templates/components/agnostic/DeleteDialog.tsx.tt +92 -0
  21. data/lib/generators/ruact/scaffold/templates/components/agnostic/Form.tsx.tt +174 -0
  22. data/lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt +253 -0
  23. data/lib/generators/ruact/scaffold/templates/controller.rb.tt +130 -0
  24. data/lib/generators/ruact/scaffold/templates/queries/application_query.rb.tt +13 -0
  25. data/lib/generators/ruact/scaffold/templates/queries/query.rb.tt +59 -0
  26. data/lib/generators/ruact/scaffold/templates/request_spec.rb.tt +77 -0
  27. data/lib/generators/ruact/scaffold/templates/views/edit.html.erb.tt +7 -0
  28. data/lib/generators/ruact/scaffold/templates/views/index.html.erb.tt +6 -0
  29. data/lib/generators/ruact/scaffold/templates/views/new.html.erb.tt +5 -0
  30. data/lib/generators/ruact/scaffold/templates/views/show.html.erb.tt +16 -0
  31. data/lib/ruact/client_manifest.rb +37 -36
  32. data/lib/ruact/component_contract.rb +115 -0
  33. data/lib/ruact/configuration.rb +80 -28
  34. data/lib/ruact/controller.rb +79 -425
  35. data/lib/ruact/doctor.rb +133 -4
  36. data/lib/ruact/erb_preprocessor.rb +71 -12
  37. data/lib/ruact/erb_preprocessor_hook.rb +4 -1
  38. data/lib/ruact/errors.rb +30 -44
  39. data/lib/ruact/html_converter.rb +22 -1
  40. data/lib/ruact/manifest_resolver.rb +149 -0
  41. data/lib/ruact/railtie.rb +70 -203
  42. data/lib/ruact/render_pipeline.rb +8 -1
  43. data/lib/ruact/server.rb +28 -6
  44. data/lib/ruact/server_functions/codegen.rb +49 -188
  45. data/lib/ruact/server_functions/codegen_v2.rb +23 -6
  46. data/lib/ruact/server_functions/codegen_v2_query_params.rb +140 -0
  47. data/lib/ruact/server_functions/error_payload.rb +1 -1
  48. data/lib/ruact/server_functions/error_rendering.rb +22 -29
  49. data/lib/ruact/server_functions/name_bridge.rb +14 -16
  50. data/lib/ruact/server_functions/query_source.rb +35 -8
  51. data/lib/ruact/server_functions/route_source.rb +3 -4
  52. data/lib/ruact/server_functions/snapshot.rb +12 -139
  53. data/lib/ruact/server_functions/validation_errors.rb +70 -0
  54. data/lib/ruact/server_functions.rb +21 -25
  55. data/lib/ruact/signed_references.rb +162 -0
  56. data/lib/ruact/string_distance.rb +72 -0
  57. data/lib/ruact/validation_errors_collector.rb +139 -0
  58. data/lib/ruact/version.rb +1 -1
  59. data/lib/ruact/view_helper.rb +109 -0
  60. data/lib/ruact.rb +20 -19
  61. data/lib/tasks/ruact.rake +10 -53
  62. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +3 -0
  63. data/spec/ruact/client_manifest_spec.rb +36 -0
  64. data/spec/ruact/component_contract_spec.rb +119 -0
  65. data/spec/ruact/configuration_spec.rb +51 -34
  66. data/spec/ruact/controller_request_spec.rb +264 -0
  67. data/spec/ruact/controller_spec.rb +70 -328
  68. data/spec/ruact/doctor_spec.rb +201 -0
  69. data/spec/ruact/erb_preprocessor_hook_spec.rb +4 -1
  70. data/spec/ruact/erb_preprocessor_spec.rb +127 -0
  71. data/spec/ruact/errors_spec.rb +0 -45
  72. data/spec/ruact/html_converter_spec.rb +50 -0
  73. data/spec/ruact/install_generator_spec.rb +591 -4
  74. data/spec/ruact/manifest_resolver_spec.rb +159 -0
  75. data/spec/ruact/query_request_spec.rb +109 -1
  76. data/spec/ruact/scaffold_generator_spec.rb +1835 -0
  77. data/spec/ruact/server_bucket_request_spec.rb +142 -0
  78. data/spec/ruact/server_function_name_spec.rb +1 -1
  79. data/spec/ruact/server_functions/codegen_spec.rb +158 -269
  80. data/spec/ruact/server_functions/name_bridge_spec.rb +9 -9
  81. data/spec/ruact/server_functions/query_source_spec.rb +51 -0
  82. data/spec/ruact/server_functions/railtie_integration_spec.rb +71 -268
  83. data/spec/ruact/server_functions/rake_spec.rb +29 -29
  84. data/spec/ruact/server_functions/snapshot_spec.rb +53 -213
  85. data/spec/ruact/server_rescue_request_spec.rb +6 -6
  86. data/spec/ruact/server_spec.rb +8 -9
  87. data/spec/ruact/signed_references_spec.rb +164 -0
  88. data/spec/ruact/string_distance_spec.rb +38 -0
  89. data/spec/ruact/validation_errors_spec.rb +116 -0
  90. data/spec/ruact/view_helper_spec.rb +79 -0
  91. data/spec/spec_helper.rb +15 -5
  92. data/vendor/javascript/ruact-server-functions-runtime/index.d.ts +40 -45
  93. data/vendor/javascript/ruact-server-functions-runtime/index.js +42 -98
  94. data/vendor/javascript/ruact-server-functions-runtime/index.test.mjs +72 -75
  95. data/vendor/javascript/ruact-server-functions-runtime/package.json +1 -1
  96. data/vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs +96 -0
  97. data/vendor/javascript/vite-plugin-ruact/index.js +372 -6
  98. data/vendor/javascript/vite-plugin-ruact/manifest-contract.test.mjs +182 -0
  99. data/vendor/javascript/vite-plugin-ruact/manifest-http.test.mjs +125 -0
  100. data/vendor/javascript/vite-plugin-ruact/package-lock.json +16 -0
  101. data/vendor/javascript/vite-plugin-ruact/package.json +3 -1
  102. data/vendor/javascript/vite-plugin-ruact/registry.test.mjs +199 -0
  103. data/vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx +68 -0
  104. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +235 -0
  105. data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +473 -0
  106. data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.mjs +114 -139
  107. data/vendor/javascript/vite-plugin-ruact/server-functions-codegen.test.mjs +199 -262
  108. data/vendor/javascript/vite-plugin-ruact/tsconfig.json +18 -0
  109. data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json +18 -0
  110. data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json +20 -0
  111. data/vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts +23 -0
  112. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx +90 -0
  113. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx +238 -0
  114. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx +339 -0
  115. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostDeleteDialog.tsx +85 -0
  116. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostForm.tsx +216 -0
  117. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx +269 -0
  118. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts +78 -0
  119. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts +166 -0
  120. data/vendor/javascript/vite-plugin-ruact/type-tests/typed-query.test-d.ts +48 -0
  121. data/vendor/javascript/vite-plugin-ruact/type-tests/usequery.test-d.ts +26 -0
  122. metadata +58 -15
  123. data/lib/generators/ruact/install/templates/application.jsx.tt +0 -51
  124. data/lib/ruact/server_action.rb +0 -131
  125. data/lib/ruact/server_functions/endpoint_controller.rb +0 -237
  126. data/lib/ruact/server_functions/registry.rb +0 -148
  127. data/lib/ruact/server_functions/registry_entry.rb +0 -26
  128. data/lib/ruact/server_functions/standalone_context.rb +0 -103
  129. data/lib/ruact/server_functions/standalone_dispatcher.rb +0 -178
  130. data/spec/ruact/server_functions/csrf_request_spec.rb +0 -380
  131. data/spec/ruact/server_functions/dispatch_request_spec.rb +0 -819
  132. data/spec/ruact/server_functions/registry_spec.rb +0 -199
  133. data/spec/ruact/server_functions/standalone_action_spec.rb +0 -224
  134. data/spec/ruact/server_functions/standalone_context_spec.rb +0 -142
  135. data/spec/ruact/server_functions/standalone_dispatcher_spec.rb +0 -273
@@ -0,0 +1,166 @@
1
+ // Story 10.2 (AC7 / Task 5) — AMBIENT stubs so the GENERATED `<Model>List.tsx`
2
+ // type-checks in ISOLATION, without a real shadcn install. The fixture
3
+ // `PostList.tsx` in this directory is byte-identical to the generator's live
4
+ // output (a gem rspec example asserts that equality), so type-checking it under
5
+ // `tsconfig.scaffold.json` proves the emitted typed List holds the FR99 server
6
+ // boundary + the shadcn `table`-primitive import contract. There is NO
7
+ // table-engine dependency — Story 10.2b moved the List off the react-table-based
8
+ // `DataTable` recipe onto the dep-free shadcn `table` primitive + a generated sort.
9
+ //
10
+ // These are deliberately MINIMAL structural shims — NOT the real shadcn / react
11
+ // types. Story 10.5 installs the real `@/components/ui/*` modules (incl. the
12
+ // `table` primitive); 10.7 wires the live end-to-end demo. The point here is the
13
+ // generated module's OWN type-safety (typed rows, sortable headers, the search
14
+ // accessor), not re-testing upstream libraries.
15
+
16
+ // JSX without `lib: ["dom"]` / `@types/react`: a permissive intrinsic-element
17
+ // table (every native prop is `any`, so `onChange={(e) => …}` etc. need no DOM
18
+ // event types). `jsx: "preserve"` in tsconfig.scaffold.json still type-checks
19
+ // every JSX expression against this namespace.
20
+ declare namespace JSX {
21
+ interface Element {} // eslint-disable-line @typescript-eslint/no-empty-interface
22
+ interface ElementClass {} // eslint-disable-line @typescript-eslint/no-empty-interface
23
+ interface IntrinsicElements {
24
+ [elem: string]: any;
25
+ }
26
+ }
27
+
28
+ declare module "react" {
29
+ export function useState<S>(initial: S | (() => S)): [S, (next: S | ((prev: S) => S)) => void];
30
+ // The generated Form's `handleSubmit(event: FormEvent)` only calls
31
+ // `event.preventDefault()`; a minimal shim keeps the type load-bearing without
32
+ // pulling in `@types/react`.
33
+ export interface FormEvent {
34
+ preventDefault(): void;
35
+ }
36
+ }
37
+
38
+ declare module "@/.ruact/server-functions" {
39
+ // FR88/FR99 wire union — the only param value type a query accessor accepts.
40
+ type Wire = string | number | boolean | null;
41
+
42
+ // The codegen exports a generic `search` from `<Plural>Query#search(q:)`,
43
+ // typed from the declared `(q:)` kwarg (Story 13.4). The List aliases it
44
+ // `search<Plural>`.
45
+ export const search: (params: { q: Wire }) => Promise<unknown>;
46
+
47
+ // Action accessors the generated Form imports (Story 10.3). Actions are NOT
48
+ // typed by FR99 (only queries are — Story 13.4 decision A), so the accessors
49
+ // take a loose payload and return `Promise<unknown>`; the Form narrows the
50
+ // result with an `as { errors?: ... } | null` assertion at the call site.
51
+ export const createPost: (payload: Record<string, unknown>) => Promise<unknown>;
52
+ export const updatePost: (payload: Record<string, unknown>) => Promise<unknown>;
53
+ // Story 10.4 — the destroy accessor the List's RowActions calls in `onConfirm`
54
+ // (DELETE /posts/:id). Like the other actions it is not FR99-typed; it returns
55
+ // `Promise<unknown>` and throws a `RuactActionError` on a non-2xx response.
56
+ export const destroyPost: (payload: Record<string, unknown>) => Promise<unknown>;
57
+
58
+ // Mirrors the runtime declaration (`ruact-server-functions-runtime/index.d.ts`)
59
+ // — `reference` is the widened accessor shape (Story 13.4), `T` the return type.
60
+ export function useQuery<T = unknown>(
61
+ reference: (...args: never[]) => Promise<unknown>,
62
+ params?: Record<string, unknown>,
63
+ ): { data: T | undefined; loading: boolean; error: unknown };
64
+ }
65
+
66
+ // Story 10.2b — the shadcn `table` primitive (a styled HTML table; no engine).
67
+ // Permissive structural shims (like Button/Badge/DropdownMenu below): the
68
+ // generated List reads `row.<attr>` directly and drives its own `useState` sort,
69
+ // so there is nothing engine-typed left to check here. Story 10.5 installs the
70
+ // real module (`npx shadcn add table`).
71
+ declare module "@/components/ui/table" {
72
+ type Props = { children?: any; [key: string]: any };
73
+ export function Table(props: Props): any;
74
+ export function TableHeader(props: Props): any;
75
+ export function TableBody(props: Props): any;
76
+ export function TableRow(props: Props): any;
77
+ export function TableHead(props: Props): any;
78
+ export function TableCell(props: Props): any;
79
+ }
80
+
81
+ declare module "@/components/ui/badge" {
82
+ export function Badge(props: { children?: any; [key: string]: any }): any;
83
+ }
84
+
85
+ declare module "@/components/ui/button" {
86
+ export function Button(props: { children?: any; [key: string]: any }): any;
87
+ }
88
+
89
+ declare module "@/components/ui/dropdown-menu" {
90
+ type Props = { children?: any; [key: string]: any };
91
+ export function DropdownMenu(props: Props): any;
92
+ export function DropdownMenuTrigger(props: Props): any;
93
+ export function DropdownMenuContent(props: Props): any;
94
+ export function DropdownMenuItem(props: Props): any;
95
+ }
96
+
97
+ // Story 10.4 — the shadcn AlertDialog parts the generated `<Model>DeleteDialog`
98
+ // imports. The controlled `AlertDialog` types `open` + `onOpenChange` precisely
99
+ // (the controlled contract the generated dialog binds); the rest stay permissive
100
+ // structural shims (like Button/Badge above). Story 10.5 installs the real module.
101
+ declare module "@/components/ui/alert-dialog" {
102
+ type Props = { children?: any; [key: string]: any };
103
+ export function AlertDialog(props: {
104
+ open?: boolean;
105
+ onOpenChange?: (open: boolean) => void;
106
+ [key: string]: any;
107
+ }): any;
108
+ export function AlertDialogTrigger(props: Props): any;
109
+ export function AlertDialogContent(props: Props): any;
110
+ export function AlertDialogHeader(props: Props): any;
111
+ export function AlertDialogFooter(props: Props): any;
112
+ export function AlertDialogTitle(props: Props): any;
113
+ export function AlertDialogDescription(props: Props): any;
114
+ export function AlertDialogCancel(props: Props): any;
115
+ export function AlertDialogAction(props: Props): any;
116
+ }
117
+
118
+ // Story 10.3 — the shadcn Form primitives the generated `<Model>Form.tsx` imports.
119
+ // Permissive structural shims (like Button/Badge above): the point is the
120
+ // generated module's OWN type-safety (controlled state, the typed action
121
+ // accessors, the `initial` prop + `{ id; label }[]` options props), not
122
+ // re-testing upstream shadcn. Story 10.5 installs the real modules.
123
+ declare module "@/components/ui/label" {
124
+ export function Label(props: { children?: any; [key: string]: any }): any;
125
+ }
126
+
127
+ // Input/Textarea/Switch/Select type the value + change handler precisely (the
128
+ // rest of the props stay permissive) so the isolated typecheck actually proves
129
+ // the generated Form binds STRING state to string controls and a boolean to the
130
+ // Switch — the controlled-state contract, not just import resolution.
131
+ declare module "@/components/ui/input" {
132
+ export function Input(props: {
133
+ value?: string;
134
+ onChange?: (event: { target: { value: string } }) => void;
135
+ [key: string]: any;
136
+ }): any;
137
+ }
138
+
139
+ declare module "@/components/ui/textarea" {
140
+ export function Textarea(props: {
141
+ value?: string;
142
+ onChange?: (event: { target: { value: string } }) => void;
143
+ [key: string]: any;
144
+ }): any;
145
+ }
146
+
147
+ declare module "@/components/ui/switch" {
148
+ export function Switch(props: {
149
+ checked?: boolean;
150
+ onCheckedChange?: (checked: boolean) => void;
151
+ [key: string]: any;
152
+ }): any;
153
+ }
154
+
155
+ declare module "@/components/ui/select" {
156
+ type Props = { children?: any; [key: string]: any };
157
+ export function Select(props: {
158
+ value?: string;
159
+ onValueChange?: (value: string) => void;
160
+ [key: string]: any;
161
+ }): any;
162
+ export function SelectContent(props: Props): any;
163
+ export function SelectItem(props: Props): any;
164
+ export function SelectTrigger(props: Props): any;
165
+ export function SelectValue(props: Props): any;
166
+ }
@@ -0,0 +1,48 @@
1
+ // Story 13.4 (AC6) — compile-time proof that the codegen-emitted typed query
2
+ // `params` signature is ENFORCED by `tsc`. This file mirrors the accessor shape
3
+ // emitted by `renderQueryExportV2` for a query declaring `def search_users(term:,
4
+ // limit: 10)` (see the byte-exact assertion in `server-functions-codegen.test.mjs`
5
+ // → "Story 13.4 … typed query params render"). If codegen drifts, that parity
6
+ // test reddens; if the emitted type is too loose, the `@ts-expect-error` lines
7
+ // below become "unused directive" tsc errors (TS2578) — either way CI catches it.
8
+ //
9
+ // `@ts-expect-error` is self-checking: it is itself an error UNLESS the next line
10
+ // genuinely fails to compile, so a clean call MUST type-check and every illegal
11
+ // call MUST fail.
12
+
13
+ // The emitted value type for a typed query param: the FR88 wire union.
14
+ type Wire = string | number | boolean | null;
15
+
16
+ // Mirror of the emitted accessor: `term` required, `limit` optional.
17
+ declare const searchUsers: (params: {
18
+ term: Wire;
19
+ limit?: Wire;
20
+ }) => Promise<unknown>;
21
+
22
+ // ── Clean calls — MUST type-check ───────────────────────────────────────────
23
+ void searchUsers({ term: "ada" });
24
+ void searchUsers({ term: "ada", limit: 10 });
25
+ void searchUsers({ term: 42, limit: true });
26
+ void searchUsers({ term: null });
27
+
28
+ // ── Missing required param — MUST error ─────────────────────────────────────
29
+ // @ts-expect-error — `term` is required
30
+ void searchUsers({ limit: 10 });
31
+
32
+ // @ts-expect-error — required param object cannot be omitted entirely
33
+ void searchUsers();
34
+
35
+ // ── Unknown key — MUST error (excess property check) ────────────────────────
36
+ // @ts-expect-error — `bogus` is not a declared param
37
+ void searchUsers({ term: "ada", bogus: 1 });
38
+
39
+ // ── Wrong-typed value — MUST error (outside the wire union) ──────────────────
40
+ // @ts-expect-error — an object is not assignable to the wire union
41
+ void searchUsers({ term: { nested: true } });
42
+
43
+ // @ts-expect-error — an array is not assignable to the wire union
44
+ void searchUsers({ term: [1, 2, 3] });
45
+
46
+ // ── Wrong arity — MUST error ────────────────────────────────────────────────
47
+ // @ts-expect-error — the accessor takes exactly one argument
48
+ void searchUsers({ term: "ada" }, "extra");
@@ -0,0 +1,26 @@
1
+ // Story 13.4 (AC3 / Task 6) — proves a codegen-emitted TYPED query accessor is
2
+ // accepted by `useQuery` without a cast (the dev⇄Codex round-1 regression: the
3
+ // pre-13.4 `useQuery` `reference` param was `(params?: Record<string, unknown>)`,
4
+ // which a typed `(params: { term: Wire }) => Promise<unknown>` accessor is NOT
5
+ // assignable to under `strict`). Also confirms the no-kwargs accessor and the
6
+ // return-type generic still work.
7
+
8
+ import { useQuery } from "ruact/server-functions-runtime";
9
+
10
+ type Wire = string | number | boolean | null;
11
+
12
+ // Mirror of the emitted accessors:
13
+ declare const searchUsers: (params: { term: Wire; limit?: Wire }) => Promise<unknown>;
14
+ declare const categories: () => Promise<unknown>;
15
+
16
+ // Typed query ref + params — MUST type-check.
17
+ void useQuery(searchUsers, { term: "ada" });
18
+ void useQuery(searchUsers, { term: "ada", limit: 25 });
19
+
20
+ // No-kwargs query ref — MUST type-check (with and without the return generic).
21
+ void useQuery(categories);
22
+ void useQuery<{ id: number }[]>(categories);
23
+
24
+ // Return-type generic still flows.
25
+ const r = useQuery<{ id: number }[]>(searchUsers, { term: "ada" });
26
+ void r.data;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruact
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luiz Garcia
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-10 00:00:00.000000000 Z
11
+ date: 2026-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -46,11 +46,32 @@ files:
46
46
  - docs/internal/README.md
47
47
  - docs/internal/decisions/server-functions-api.md
48
48
  - lib/generators/ruact/install/install_generator.rb
49
- - lib/generators/ruact/install/templates/application.jsx.tt
49
+ - lib/generators/ruact/install/templates/Procfile.dev.tt
50
+ - lib/generators/ruact/install/templates/dev.tt
50
51
  - lib/generators/ruact/install/templates/initializer.rb.tt
52
+ - lib/generators/ruact/install/templates/package.json.tt
51
53
  - lib/generators/ruact/install/templates/vite.config.js.tt
54
+ - lib/generators/ruact/scaffold/scaffold_attribute.rb
55
+ - lib/generators/ruact/scaffold/scaffold_form_helpers.rb
56
+ - lib/generators/ruact/scaffold/scaffold_generator.rb
57
+ - lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb
58
+ - lib/generators/ruact/scaffold/templates/components/DeleteDialog.tsx.tt
59
+ - lib/generators/ruact/scaffold/templates/components/Form.tsx.tt
60
+ - lib/generators/ruact/scaffold/templates/components/List.tsx.tt
61
+ - lib/generators/ruact/scaffold/templates/components/agnostic/DeleteDialog.tsx.tt
62
+ - lib/generators/ruact/scaffold/templates/components/agnostic/Form.tsx.tt
63
+ - lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt
64
+ - lib/generators/ruact/scaffold/templates/controller.rb.tt
65
+ - lib/generators/ruact/scaffold/templates/queries/application_query.rb.tt
66
+ - lib/generators/ruact/scaffold/templates/queries/query.rb.tt
67
+ - lib/generators/ruact/scaffold/templates/request_spec.rb.tt
68
+ - lib/generators/ruact/scaffold/templates/views/edit.html.erb.tt
69
+ - lib/generators/ruact/scaffold/templates/views/index.html.erb.tt
70
+ - lib/generators/ruact/scaffold/templates/views/new.html.erb.tt
71
+ - lib/generators/ruact/scaffold/templates/views/show.html.erb.tt
52
72
  - lib/ruact.rb
53
73
  - lib/ruact/client_manifest.rb
74
+ - lib/ruact/component_contract.rb
54
75
  - lib/ruact/configuration.rb
55
76
  - lib/ruact/controller.rb
56
77
  - lib/ruact/doctor.rb
@@ -64,6 +85,7 @@ files:
64
85
  - lib/ruact/flight/row_emitter.rb
65
86
  - lib/ruact/flight/serializer.rb
66
87
  - lib/ruact/html_converter.rb
88
+ - lib/ruact/manifest_resolver.rb
67
89
  - lib/ruact/query.rb
68
90
  - lib/ruact/railtie.rb
69
91
  - lib/ruact/render_context.rb
@@ -71,13 +93,12 @@ files:
71
93
  - lib/ruact/routing.rb
72
94
  - lib/ruact/serializable.rb
73
95
  - lib/ruact/server.rb
74
- - lib/ruact/server_action.rb
75
96
  - lib/ruact/server_functions.rb
76
97
  - lib/ruact/server_functions/backtrace_cleaner.rb
77
98
  - lib/ruact/server_functions/bucket_two_payload.rb
78
99
  - lib/ruact/server_functions/codegen.rb
79
100
  - lib/ruact/server_functions/codegen_v2.rb
80
- - lib/ruact/server_functions/endpoint_controller.rb
101
+ - lib/ruact/server_functions/codegen_v2_query_params.rb
81
102
  - lib/ruact/server_functions/error_payload.rb
82
103
  - lib/ruact/server_functions/error_rendering.rb
83
104
  - lib/ruact/server_functions/error_suggestion.rb
@@ -85,13 +106,13 @@ files:
85
106
  - lib/ruact/server_functions/query_context.rb
86
107
  - lib/ruact/server_functions/query_dispatch.rb
87
108
  - lib/ruact/server_functions/query_source.rb
88
- - lib/ruact/server_functions/registry.rb
89
- - lib/ruact/server_functions/registry_entry.rb
90
109
  - lib/ruact/server_functions/route_source.rb
91
110
  - lib/ruact/server_functions/snapshot.rb
92
111
  - lib/ruact/server_functions/snapshot_writer.rb
93
- - lib/ruact/server_functions/standalone_context.rb
94
- - lib/ruact/server_functions/standalone_dispatcher.rb
112
+ - lib/ruact/server_functions/validation_errors.rb
113
+ - lib/ruact/signed_references.rb
114
+ - lib/ruact/string_distance.rb
115
+ - lib/ruact/validation_errors_collector.rb
95
116
  - lib/ruact/version.rb
96
117
  - lib/ruact/view_helper.rb
97
118
  - lib/rubocop/cop/ruact.rb
@@ -125,7 +146,9 @@ files:
125
146
  - spec/fixtures/flight/string_dollar_escape.txt
126
147
  - spec/fixtures/flight/undefined.txt
127
148
  - spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb
149
+ - spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb
128
150
  - spec/ruact/client_manifest_spec.rb
151
+ - spec/ruact/component_contract_spec.rb
129
152
  - spec/ruact/configuration_spec.rb
130
153
  - spec/ruact/controller_request_spec.rb
131
154
  - spec/ruact/controller_spec.rb
@@ -137,20 +160,20 @@ files:
137
160
  - spec/ruact/flight/serializer_spec.rb
138
161
  - spec/ruact/html_converter_spec.rb
139
162
  - spec/ruact/install_generator_spec.rb
163
+ - spec/ruact/manifest_resolver_spec.rb
140
164
  - spec/ruact/query_request_spec.rb
141
165
  - spec/ruact/query_spec.rb
142
166
  - spec/ruact/railtie_spec.rb
143
167
  - spec/ruact/render_context_spec.rb
144
168
  - spec/ruact/render_pipeline_concurrency_spec.rb
145
169
  - spec/ruact/render_pipeline_spec.rb
170
+ - spec/ruact/scaffold_generator_spec.rb
146
171
  - spec/ruact/serializable_spec.rb
147
172
  - spec/ruact/server_bucket_request_spec.rb
148
173
  - spec/ruact/server_function_name_spec.rb
149
174
  - spec/ruact/server_functions/backtrace_cleaner_spec.rb
150
175
  - spec/ruact/server_functions/bucket_two_payload_spec.rb
151
176
  - spec/ruact/server_functions/codegen_spec.rb
152
- - spec/ruact/server_functions/csrf_request_spec.rb
153
- - spec/ruact/server_functions/dispatch_request_spec.rb
154
177
  - spec/ruact/server_functions/error_payload_spec.rb
155
178
  - spec/ruact/server_functions/error_suggestion_spec.rb
156
179
  - spec/ruact/server_functions/name_bridge_spec.rb
@@ -158,16 +181,15 @@ files:
158
181
  - spec/ruact/server_functions/query_source_spec.rb
159
182
  - spec/ruact/server_functions/railtie_integration_spec.rb
160
183
  - spec/ruact/server_functions/rake_spec.rb
161
- - spec/ruact/server_functions/registry_spec.rb
162
184
  - spec/ruact/server_functions/route_source_spec.rb
163
185
  - spec/ruact/server_functions/snapshot_spec.rb
164
186
  - spec/ruact/server_functions/snapshot_writer_spec.rb
165
- - spec/ruact/server_functions/standalone_action_spec.rb
166
- - spec/ruact/server_functions/standalone_context_spec.rb
167
- - spec/ruact/server_functions/standalone_dispatcher_spec.rb
168
187
  - spec/ruact/server_rescue_request_spec.rb
169
188
  - spec/ruact/server_spec.rb
170
189
  - spec/ruact/server_upload_request_spec.rb
190
+ - spec/ruact/signed_references_spec.rb
191
+ - spec/ruact/string_distance_spec.rb
192
+ - spec/ruact/validation_errors_spec.rb
171
193
  - spec/ruact/view_helper_spec.rb
172
194
  - spec/spec_helper.rb
173
195
  - spec/support/fixtures/pixel.png
@@ -181,11 +203,32 @@ files:
181
203
  - vendor/javascript/ruact-server-functions-runtime/index.test.mjs
182
204
  - vendor/javascript/ruact-server-functions-runtime/package.json
183
205
  - vendor/javascript/ruact-server-functions-runtime/usequery.test.mjs
206
+ - vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs
184
207
  - vendor/javascript/vite-plugin-ruact/index.js
208
+ - vendor/javascript/vite-plugin-ruact/manifest-contract.test.mjs
209
+ - vendor/javascript/vite-plugin-ruact/manifest-http.test.mjs
185
210
  - vendor/javascript/vite-plugin-ruact/package-lock.json
186
211
  - vendor/javascript/vite-plugin-ruact/package.json
212
+ - vendor/javascript/vite-plugin-ruact/registry.test.mjs
213
+ - vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx
214
+ - vendor/javascript/vite-plugin-ruact/runtime/flight-client.js
215
+ - vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js
187
216
  - vendor/javascript/vite-plugin-ruact/server-functions-codegen.mjs
188
217
  - vendor/javascript/vite-plugin-ruact/server-functions-codegen.test.mjs
218
+ - vendor/javascript/vite-plugin-ruact/tsconfig.json
219
+ - vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json
220
+ - vendor/javascript/vite-plugin-ruact/tsconfig.scaffold.json
221
+ - vendor/javascript/vite-plugin-ruact/type-tests/emitted-module.test-d.ts
222
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostDeleteDialog.tsx
223
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostForm.tsx
224
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx
225
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostDeleteDialog.tsx
226
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostForm.tsx
227
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx
228
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts
229
+ - vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts
230
+ - vendor/javascript/vite-plugin-ruact/type-tests/typed-query.test-d.ts
231
+ - vendor/javascript/vite-plugin-ruact/type-tests/usequery.test-d.ts
189
232
  - vendor/javascript/vite-plugin-ruact/vitest.config.mjs
190
233
  homepage: https://luizcg.github.io/ruact/
191
234
  licenses:
@@ -1,51 +0,0 @@
1
- import { createRoot } from 'react-dom/client';
2
- import { useState, useEffect } from 'react';
3
- import { createFromFlightPayload } from './flight-client.js';
4
- import { setupRouter, teardownRouter } from './ruact-router.js';
5
-
6
- // MODULE_REGISTRY maps react-client-manifest "id" values to component exports.
7
- // Add each "use client" component here as you create it.
8
- // Keys must match the "id" field in public/react-client-manifest.json.
9
- //
10
- // Example:
11
- // import { MyButton } from './components/MyButton.jsx';
12
- // const MODULE_REGISTRY = { '/MyButton.jsx': { MyButton } };
13
- const MODULE_REGISTRY = {};
14
-
15
- // ---------------------------------------------------------------------------
16
- // Boot
17
- // ---------------------------------------------------------------------------
18
- const flightData = globalThis.__FLIGHT_DATA;
19
-
20
- if (!flightData || flightData.length === 0) {
21
- // Non-RSC page or Rails server not running — skip hydration.
22
- const root = document.getElementById('root');
23
- if (root && root.childNodes.length === 0) {
24
- root.textContent = '[ruact] No Flight data found — is the Rails server running?';
25
- }
26
- } else {
27
- const payload = flightData.join('');
28
-
29
- let initialTree;
30
- try {
31
- initialTree = createFromFlightPayload(payload, MODULE_REGISTRY);
32
- } catch (err) {
33
- console.error('[ruact] Failed to parse Flight payload:', err);
34
- const root = document.getElementById('root');
35
- if (root) root.textContent = '[ruact] Error: ' + err.message;
36
- throw err;
37
- }
38
-
39
- function App() {
40
- const [tree, setTree] = useState(() => initialTree);
41
-
42
- useEffect(() => {
43
- setupRouter({ onNavigate: setTree, moduleRegistry: MODULE_REGISTRY });
44
- return () => teardownRouter();
45
- }, []);
46
-
47
- return tree;
48
- }
49
-
50
- createRoot(document.getElementById('root')).render(<App />);
51
- }
@@ -1,131 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ruact
4
- # Story 8.3 — host module for STANDALONE server actions. The Ruby
5
- # equivalent of React's `"use server"` directive: a module under
6
- # `app/server_actions/` that `extend`s {Ruact::ServerAction} and declares
7
- # one action body per file with the `ruact_action` macro.
8
- #
9
- # # app/server_actions/create_post.rb
10
- # module CreatePost
11
- # extend Ruact::ServerAction
12
- #
13
- # ruact_action :create_post do |params|
14
- # Post.create!(title: params[:title], body: params[:body])
15
- # end
16
- # end
17
- #
18
- # The React side cannot tell standalone-hosted actions apart from
19
- # controller-hosted ones — both export under the same accessor at
20
- # `app/javascript/.ruact/server-functions.ts` (Story 8.0a codegen).
21
- #
22
- # Differences from {Ruact::Controller#ruact_action} (the controller-hosted
23
- # path from Story 8.1):
24
- #
25
- # - NO instance method is defined on the host module. There is no
26
- # `host.public_send(action_name)` call shape — the dispatcher
27
- # ({Ruact::ServerFunctions::EndpointController}) detects the standalone
28
- # host shape and routes through {Ruact::ServerFunctions::StandaloneDispatcher}
29
- # instead of `host_class.dispatch`. Standalone modules are reachable
30
- # ONLY through the gem's `POST /__ruact/fn/:name` endpoint; there is
31
- # no public Ruby surface to invoke them, by design.
32
- # - NO controller `before_action` chain runs. The dev opts out of the
33
- # controller-DSL ergonomic by picking the standalone path; the
34
- # trade-off is no implicit auth/scoping. The dev calls `current_user`
35
- # (resolved via {Ruact::Configuration#current_user_resolver}) and
36
- # Pundit / ActionPolicy directly inside the block when needed.
37
- # - NO `method_added` hook, NO `Thread.current[:__ruact_dispatching]`
38
- # sentinel. The standalone path has no routing surface that could
39
- # reach the action body outside of the gem-managed endpoint, so the
40
- # controller-only security guards are unnecessary (and would be
41
- # misleading — the block is stored in the registry, not on the
42
- # module as an instance method).
43
- #
44
- # Shared with {Ruact::Controller#ruact_action}:
45
- #
46
- # - {Ruact::ServerFunctions::Registry} via `Ruact.action_registry`.
47
- # Symbols declared by standalone modules and by controller hosts live
48
- # in the same registry instance; the existing collision detector
49
- # ({Ruact::ServerFunctions::Registry#detect_collision!}) catches
50
- # same-symbol-different-host collisions across both host shapes.
51
- # - The naming-bridge rule ({Ruact::ServerFunctions::NameBridge}) and
52
- # reserved-identifier sets (`RESERVED_JS_IDENTIFIERS`,
53
- # `RESERVED_BY_RUACT`).
54
- # - The block-parameter shape guard (one positional argument, no
55
- # required keyword arguments) — same regex as Story 8.1 Re-run-5.
56
- module ServerAction
57
- # @param symbol [Symbol] the action name; same naming-bridge rule as
58
- # {Ruact::Controller#ruact_action}.
59
- # @yield [params] the block runs via `instance_exec` on a fresh
60
- # {Ruact::ServerFunctions::StandaloneContext} at dispatch time.
61
- # @return [Ruact::ServerFunctions::RegistryEntry] the entry just registered.
62
- # @raise [ArgumentError] when +symbol+ is not a Symbol, the block is
63
- # missing, or the block's parameter shape is rejected.
64
- # @raise [Ruact::ConfigurationError] when the symbol fails the
65
- # naming-bridge rule or collides with another `ruact_action` in the
66
- # registry (mixed-host collisions are caught here too — see AC4).
67
- def ruact_action(symbol, &block)
68
- # Story 8.3 review R4 — reject Class hosts loudly. `extend Ruact::ServerAction`
69
- # on a Class would work at extend time (Class < Module), but the
70
- # endpoint dispatcher's `standalone_host?` predicate returns false for
71
- # Class hosts (it requires Module-NOT-Class), and the controller-DSL
72
- # branch would then try `host_class.dispatch(...)` against a class
73
- # that has NO Rails dispatch surface — crashing at request time.
74
- # Catch this at declaration time so the failure is visible at boot.
75
- if is_a?(Class)
76
- raise Ruact::ConfigurationError,
77
- "Ruact::ServerAction is intended for standalone HOST MODULES under " \
78
- "`app/server_actions/`. You extended it onto #{name || self} which " \
79
- "is a Class — that's a controller-shape host. For controller-hosted " \
80
- "actions use `include Ruact::Controller` and declare `ruact_action` " \
81
- "inside the controller class body; for standalone actions declare a " \
82
- "`module Foo; extend Ruact::ServerAction; ...; end` instead."
83
- end
84
-
85
- unless symbol.is_a?(Symbol)
86
- raise ArgumentError,
87
- "ruact_action requires a Symbol argument, got " \
88
- "#{symbol.inspect} (#{symbol.class}). Use " \
89
- "`ruact_action :#{symbol}` not `ruact_action #{symbol.inspect}`."
90
- end
91
-
92
- unless block
93
- raise ArgumentError,
94
- "ruact_action :#{symbol} (standalone) requires a block — declare the " \
95
- "implementation with `ruact_action :#{symbol} do |params| ... end`"
96
- end
97
-
98
- # Mirror the block-parameter shape guard from Story 8.1 Re-run-5
99
- # (controller.rb:125-137). The standalone dispatcher invokes the
100
- # block with one positional argument (the params shadow); blocks
101
- # with wrong arity / required kwargs would crash at dispatch time.
102
- req_count = block.parameters.count { |kind, _| kind == :req }
103
- opt_count = block.parameters.count { |kind, _| kind == :opt }
104
- rest_count = block.parameters.count { |kind, _| kind == :rest }
105
- named_positional = req_count + opt_count
106
- positional_total = named_positional + rest_count
107
- has_required_kwarg = block.parameters.any? { |kind, _| kind == :keyreq }
108
- if positional_total.zero? || named_positional > 1 || has_required_kwarg
109
- raise ArgumentError,
110
- "ruact_action :#{symbol} (standalone) block must accept exactly one " \
111
- "positional parameter and no required keyword arguments " \
112
- "(got parameters=#{block.parameters.inspect}). Use " \
113
- "`ruact_action :#{symbol} do |params| ... end`."
114
- end
115
-
116
- # NOTE: no `FRAMEWORK_RESERVED_METHODS` check — standalone modules
117
- # have no ActionController surface to clobber. NameBridge +
118
- # RESERVED_JS_IDENTIFIERS + RESERVED_BY_RUACT still fire via the
119
- # registry's `register` path below.
120
- #
121
- # NOTE: no `own_methods` / inherited-helper guard — there is no
122
- # `define_method` step that could shadow anything; the block lives
123
- # in the registry, not on the module.
124
- #
125
- # NOTE: no `method_added` hook — standalone modules don't define
126
- # instance methods at all, so a same-name `def` cannot shadow
127
- # anything (Pitfall #2 in the story spec).
128
- Ruact.action_registry.register(symbol, kind: :action, controller: self, &block)
129
- end
130
- end
131
- end