weft 0.1.0 → 0.2.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +134 -28
  3. data/README.md +46 -23
  4. data/docs/app-patterns.md +8 -7
  5. data/docs/arbre.md +49 -18
  6. data/docs/configuration.md +42 -35
  7. data/docs/dsl.md +356 -105
  8. data/docs/error-handling.md +64 -24
  9. data/docs/examples/active-search.md +10 -10
  10. data/docs/examples/browser-dialogs.md +10 -10
  11. data/docs/examples/bulk-update.md +11 -11
  12. data/docs/examples/click-to-edit.md +14 -14
  13. data/docs/examples/click-to-load.md +8 -8
  14. data/docs/examples/delete-row.md +17 -19
  15. data/docs/examples/edit-row.md +17 -14
  16. data/docs/examples/file-upload.md +5 -5
  17. data/docs/examples/infinite-scroll.md +8 -8
  18. data/docs/examples/inline-expansion.md +8 -8
  19. data/docs/examples/inline-validation.md +17 -17
  20. data/docs/examples/lazy-loading.md +8 -8
  21. data/docs/examples/live-ticker.md +1 -1
  22. data/docs/examples/modal-dialog.md +3 -3
  23. data/docs/examples/progress-bar.md +1 -1
  24. data/docs/examples/reset-user-input.md +7 -7
  25. data/docs/examples/tabs.md +4 -4
  26. data/docs/examples/tooltip.md +8 -8
  27. data/docs/examples/updating-other-content.md +9 -9
  28. data/docs/examples/value-select.md +11 -11
  29. data/docs/params.md +112 -0
  30. data/docs/routing.md +13 -13
  31. data/docs/tutorial.md +46 -48
  32. data/lib/weft/action.rb +4 -2
  33. data/lib/weft/autoloading.rb +69 -0
  34. data/lib/weft/component.rb +97 -31
  35. data/lib/weft/configuration.rb +37 -5
  36. data/lib/weft/context/expansion.rb +184 -0
  37. data/lib/weft/context/interception.rb +22 -2
  38. data/lib/weft/context/modifiers.rb +78 -0
  39. data/lib/weft/context/traversal.rb +80 -0
  40. data/lib/weft/context/wiring.rb +85 -0
  41. data/lib/weft/context.rb +70 -164
  42. data/lib/weft/defaults/error_component.rb +57 -21
  43. data/lib/weft/defaults/error_page.rb +12 -10
  44. data/lib/weft/defaults/not_found_component.rb +14 -12
  45. data/lib/weft/defaults/not_found_page.rb +9 -8
  46. data/lib/weft/dsl/actions.rb +9 -9
  47. data/lib/weft/dsl/inclusions.rb +48 -11
  48. data/lib/weft/dsl/params.rb +265 -0
  49. data/lib/weft/dsl/recoveries.rb +36 -6
  50. data/lib/weft/dsl/sandbox.rb +26 -0
  51. data/lib/weft/dsl/triggers.rb +28 -8
  52. data/lib/weft/dsl/updates.rb +31 -8
  53. data/lib/weft/error.rb +12 -1
  54. data/lib/weft/page/assets.rb +222 -0
  55. data/lib/weft/page/head.rb +87 -0
  56. data/lib/weft/page.rb +55 -239
  57. data/lib/weft/params/assembly.rb +170 -0
  58. data/lib/weft/params.rb +138 -0
  59. data/lib/weft/presets.rb +96 -0
  60. data/lib/weft/redirect.rb +7 -7
  61. data/lib/weft/registry/eligibility.rb +5 -19
  62. data/lib/weft/registry.rb +58 -18
  63. data/lib/weft/resolver.rb +48 -20
  64. data/lib/weft/router/actions.rb +106 -22
  65. data/lib/weft/router/errors.rb +223 -83
  66. data/lib/weft/router/oob_includes.rb +202 -17
  67. data/lib/weft/router/streaming.rb +86 -20
  68. data/lib/weft/router.rb +33 -25
  69. data/lib/weft/version.rb +1 -1
  70. data/lib/weft.rb +37 -24
  71. metadata +32 -8
  72. data/lib/weft/attributes.rb +0 -65
  73. data/lib/weft/dsl/attributes.rb +0 -43
  74. data/lib/weft/shorthands.rb +0 -57
@@ -9,14 +9,37 @@ Weft.configure do |c|
9
9
  end
10
10
  ```
11
11
 
12
- Call `Weft.configure` from your boot file (typically `config/environment.rb`), after `require "weft"` and before the first request. Settings are validated as they're assigned, so a typo'd value raises immediately at boot rather than misbehaving later. After the block runs, Weft applies any side effects the new settings imply (mounting static asset routes, enabling the reloader, setting the logger level). Calling `Weft.configure` more than once is fine — each call re-applies these side effects idempotently.
12
+ Call `Weft.configure` from your boot file (typically `config/environment.rb`), after `require "weft"` and before the first request. Settings are validated as they're assigned, so a typo'd value raises immediately at boot rather than misbehaving later. After the block runs, Weft applies any side effects the new settings imply (mounting static asset routes, setting the logger level). Calling `Weft.configure` more than once is fine — each call re-applies these side effects idempotently.
13
+
14
+ ## Autoloading (`Weft.configure_autoloading`)
15
+
16
+ `Weft.configure` has an older sibling for the one thing that can't wait for a configure block: loading your code. `Weft.configure_autoloading` puts [Zeitwerk](https://github.com/fxn/zeitwerk) in charge of your application directories — one call, in your boot file, **before** `Weft.configure`:
17
+
18
+ ```ruby
19
+ Weft.configure_autoloading(
20
+ paths: [File.join(APP_ROOT, "app", "components"),
21
+ File.join(APP_ROOT, "app", "pages"),
22
+ File.join(APP_ROOT, "app", "models")],
23
+ inflections: { "dropship_ui" => "DropshipUI" },
24
+ reload: ENV.fetch("RACK_ENV", "production") == "development"
25
+ )
26
+ ```
27
+
28
+ - **`paths:`** — the directories Zeitwerk manages. Files follow Zeitwerk's conventions: one constant per file, named for it (`order_card.rb` ↔ `OrderCard`, `oms/order_header.rb` ↔ `Oms::OrderHeader`). Everything is eager-loaded on the spot — Weft routes from its registry, which populates as classes load, so your components and pages must exist before the first request.
29
+ - **`inflections:`** — acronym and irregular-name mappings, passed straight to Zeitwerk's inflector (file basename → constant name).
30
+ - **`reload:`** — when `true`, code changes take effect without restarting the server: constants reload on every request, classes are evicted from Weft's registry the moment Zeitwerk unloads them (so a deleted file's route disappears too, instead of lingering), and the class-valued settings below rebind to their fresh definitions after each reload. Leave it `false` (the default) in production — constants load once and nothing ever unloads.
31
+
32
+ Call order is the reason this isn't a `configure` setting: the call loads your code *immediately*, so by the time a `Weft.configure` block references one of your classes (`c.error_page = ErrorPage`), the constant resolves. Autoload first, configure second.
33
+
34
+ Two things to know when reloading is on:
35
+
36
+ - **Weft declarations belong in class bodies.** Boot files run once; class bodies re-run on every reload. A `SomePage.recovers ...` call in `environment.rb` evaporates at the first reload — the same declaration inside the class body is re-applied every time.
37
+ - **Bring-your-own-loader works too.** If you manage Zeitwerk (or any reloader) yourself, keep Weft's registry in sync by evicting classes as they unload — `loader.on_unload { |_cpath, klass, _path| Weft.registry.evict(klass) }` — or reset wholesale with `Weft.registry.clear` before each reload; then call `Weft.configuration.refresh_stale_classes!` after reloading so class-valued settings rebind. (See [Routing](routing.md) for the registry's side of this.)
13
38
 
14
39
  Every setting, at a glance:
15
40
 
16
41
  | Setting | Default | Purpose |
17
42
  | --- | --- | --- |
18
- | [`auto_reload`](#auto_reload) | `false` | Enable code reloading on the Router during development. |
19
- | [`reload_paths`](#reload_paths) | `[]` | Files the reloader should watch, as glob patterns. |
20
43
  | [`router_logging`](#router_logging) | `false` | Request logging on the Router. |
21
44
  | [`log_level`](#log_level) | `:info` | Severity threshold for `Weft.logger`. |
22
45
  | [`include_htmx`](#include_htmx) | `true` | Pages include the htmx script automatically. |
@@ -24,6 +47,7 @@ Every setting, at a glance:
24
47
  | [`static_assets`](#static_assets) | none | Serve a directory of files at a URL prefix. |
25
48
  | [`component_path`](#component_path) | derives `/_components/<name>` | How a component class maps to its route. |
26
49
  | [`stream_suffix`](#stream_suffix) | `"_stream"` | Path segment for SSE stream endpoints. |
50
+ | [`push_attempts`](#push_attempts) | `3` | Consecutive failed pushes before a stream closes. |
27
51
  | [`error_component`](#the-four-fallback-targets) | `Weft::Defaults::ErrorComponent` | Fragment rendered when a component fails. |
28
52
  | [`error_page`](#the-four-fallback-targets) | `Weft::Defaults::ErrorPage` | Document rendered when a page fails. |
29
53
  | [`not_found_component`](#the-four-fallback-targets) | `Weft::Defaults::NotFoundComponent` | Fragment rendered for a component-context 404. |
@@ -33,38 +57,6 @@ Every setting, at a glance:
33
57
 
34
58
  ## Development
35
59
 
36
- ### `auto_reload`
37
-
38
- Default: `false`.
39
-
40
- When `true`, Weft registers `Sinatra::Reloader` on `Weft::Router`, so code changes are picked up without restarting the server. Off by default; flip it on however you detect development mode:
41
-
42
- ```ruby
43
- Weft.configure do |c|
44
- c.auto_reload = (ENV.fetch("RACK_ENV", "production") == "development")
45
- c.reload_paths = [File.expand_path("app/**/*.rb", __dir__)]
46
- end
47
- ```
48
-
49
- The reloader is registered once, the first time a `Weft.configure` call sees `auto_reload` set to `true` — it can't be unregistered afterward, and `reload_paths` entries added in later `configure` calls won't be picked up. Set both together, as above.
50
-
51
- `auto_reload` suits apps that don't already have a reloading story. If your app manages its own constant loading with Zeitwerk, you may prefer to drive reloading yourself (`loader.reload` in a `Weft::Router.before` block) and leave this off. Either way, Weft's registry tolerates reloading: when a class is redefined, the stale registration is pruned automatically (see [Routing](routing.md)).
52
-
53
- ### `reload_paths`
54
-
55
- Default: `[]`.
56
-
57
- Glob patterns added to the reloader's watch list. Without this, `Sinatra::Reloader` only watches files where Weft defines its own routes — meaning edits to *your* components and pages would go unnoticed. Point it at your application code:
58
-
59
- ```ruby
60
- c.reload_paths = [
61
- File.expand_path("app/**/*.rb", __dir__),
62
- File.expand_path("config/**/*.rb", __dir__)
63
- ]
64
- ```
65
-
66
- Only meaningful alongside `auto_reload = true`, and must be set in the same `configure` call (or an earlier one).
67
-
68
60
  ### `router_logging`
69
61
 
70
62
  Default: `false`.
@@ -147,6 +139,15 @@ register_script "https://cdn.example.com/widgets.js",
147
139
 
148
140
  (The htmx core and SSE-extension scripts Weft includes itself are integrity-pinned the same way.)
149
141
 
142
+ For the snippets that don't warrant a file, `register_inline_css` and `register_inline_js` embed CSS and JavaScript directly in the head — each registered string emits as its own `<style>`/`<script>` tag, subclasses add atop their parents' registrations, and inline JS lands after the external scripts so registered libraries are in reach:
143
+
144
+ ```ruby
145
+ class ApplicationPage < Weft::Page
146
+ register_inline_css ".mono { font-family: monospace; }"
147
+ register_inline_js "htmx.config.defaultSwapDelay = 50;"
148
+ end
149
+ ```
150
+
150
151
  Registering a duplicate bundle name, or a second bundle at the same root, raises `Weft::InvalidConfiguration` — bundles are declared once, at boot.
151
152
 
152
153
  ## Routing
@@ -217,3 +218,9 @@ How errors present when the failing request came from htmx *and* the error fell
217
218
  - `:redirect` — send the client to the error page instead (via an `HX-Redirect` header), abandoning the current page. Some apps prefer a full-page failure posture over patchwork error states.
218
219
 
219
220
  Two carve-outs to know about: explicit `recovers` targets you declare are never overridden — this setting only governs the gem-default fallthrough — and `Weft::NotFound` is exempt, so a missing record renders as an in-place not-found fragment even under `:redirect`.
221
+
222
+ ### `push_attempts`
223
+
224
+ Default: `3`.
225
+
226
+ How many consecutive failed pushes a live stream tolerates before giving up. Each failure delivers a recovery frame through the failing component's `recovers` chain; when the budget runs out, the stream pushes its final frame, instructs the browser not to reconnect, and closes. A successful push resets the count, and any component can override the gem-wide value with `pushes every: ..., attempts: ...`. Must be an integer of at least 1. See [Error handling on live streams](error-handling.md#error-handling-on-live-streams).