weft 0.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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +37 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +150 -0
  5. data/docs/app-patterns.md +268 -0
  6. data/docs/arbre.md +298 -0
  7. data/docs/configuration.md +219 -0
  8. data/docs/dsl.md +329 -0
  9. data/docs/error-handling.md +136 -0
  10. data/docs/examples/README.md +70 -0
  11. data/docs/examples/active-search.md +125 -0
  12. data/docs/examples/browser-dialogs.md +71 -0
  13. data/docs/examples/bulk-update.md +122 -0
  14. data/docs/examples/click-to-edit.md +113 -0
  15. data/docs/examples/click-to-load.md +77 -0
  16. data/docs/examples/delete-row.md +101 -0
  17. data/docs/examples/edit-row.md +146 -0
  18. data/docs/examples/file-upload.md +96 -0
  19. data/docs/examples/infinite-scroll.md +98 -0
  20. data/docs/examples/inline-expansion.md +98 -0
  21. data/docs/examples/inline-validation.md +101 -0
  22. data/docs/examples/keyboard-shortcuts.md +71 -0
  23. data/docs/examples/lazy-loading.md +96 -0
  24. data/docs/examples/live-ticker.md +91 -0
  25. data/docs/examples/modal-dialog.md +88 -0
  26. data/docs/examples/progress-bar.md +138 -0
  27. data/docs/examples/reset-user-input.md +101 -0
  28. data/docs/examples/tabs.md +102 -0
  29. data/docs/examples/tooltip.md +88 -0
  30. data/docs/examples/updating-other-content.md +169 -0
  31. data/docs/examples/value-select.md +109 -0
  32. data/docs/routing.md +131 -0
  33. data/docs/tutorial.md +372 -0
  34. data/lib/weft/action.rb +83 -0
  35. data/lib/weft/attributes.rb +65 -0
  36. data/lib/weft/component.rb +176 -0
  37. data/lib/weft/configuration.rb +177 -0
  38. data/lib/weft/context/interception.rb +22 -0
  39. data/lib/weft/context.rb +184 -0
  40. data/lib/weft/defaults/error_component.rb +63 -0
  41. data/lib/weft/defaults/error_page.rb +27 -0
  42. data/lib/weft/defaults/not_found_component.rb +44 -0
  43. data/lib/weft/defaults/not_found_page.rb +25 -0
  44. data/lib/weft/defaults.rb +9 -0
  45. data/lib/weft/dsl/actions.rb +72 -0
  46. data/lib/weft/dsl/attributes.rb +43 -0
  47. data/lib/weft/dsl/containers.rb +77 -0
  48. data/lib/weft/dsl/inclusions.rb +49 -0
  49. data/lib/weft/dsl/recoveries.rb +89 -0
  50. data/lib/weft/dsl/triggers.rb +40 -0
  51. data/lib/weft/dsl/updates.rb +86 -0
  52. data/lib/weft/error.rb +64 -0
  53. data/lib/weft/page.rb +371 -0
  54. data/lib/weft/redirect.rb +45 -0
  55. data/lib/weft/registry/eligibility.rb +58 -0
  56. data/lib/weft/registry.rb +202 -0
  57. data/lib/weft/resolver.rb +33 -0
  58. data/lib/weft/router/actions.rb +77 -0
  59. data/lib/weft/router/errors.rb +246 -0
  60. data/lib/weft/router/oob_includes.rb +34 -0
  61. data/lib/weft/router/streaming.rb +63 -0
  62. data/lib/weft/router.rb +191 -0
  63. data/lib/weft/shorthands.rb +57 -0
  64. data/lib/weft/version.rb +5 -0
  65. data/lib/weft.rb +124 -0
  66. metadata +169 -0
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Weft
4
+ # Registry for named interaction shorthands. Each shorthand is a preset of
5
+ # trigger/swap/target defaults that Context expands via loads:.
6
+ #
7
+ # Shipped presets are registered at the bottom of this file. Users will be
8
+ # able to register custom shorthands in v1.x via the same API.
9
+ module Shorthands
10
+ class << self
11
+ # Register a named interaction shorthand.
12
+ #
13
+ # Weft::Shorthands.register :tooltip, trigger: :hover, swap: :fill
14
+ def register(name, **defaults)
15
+ registry[name] = defaults
16
+ end
17
+
18
+ # Look up a registered shorthand by name. Returns the defaults hash or nil.
19
+ def lookup(name)
20
+ registry[name]
21
+ end
22
+
23
+ # All registered shorthand names.
24
+ def registered
25
+ registry.keys
26
+ end
27
+
28
+ private
29
+
30
+ def registry
31
+ @registry ||= {}
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # Shipped interaction shorthands. Adding a new one is one line.
38
+ # The mechanism (Context dispatch + loads: expansion) handles the rest.
39
+ #
40
+ # NOTE: "shorthand" naming is provisional — may be renamed to "pattern"
41
+ # or similar after evaluation (Mission 29).
42
+
43
+ Weft::Shorthands.register :tooltip, trigger: :hover, swap: :fill
44
+ Weft::Shorthands.register :inline_expand, trigger: :click, swap: :after
45
+ Weft::Shorthands.register :lazy, trigger: :visible, swap: :fill, target: :self
46
+ Weft::Shorthands.register :modal, trigger: :click, swap: :fill
47
+ Weft::Shorthands.register :load_more, trigger: :click, swap: :replace, target: :self
48
+ Weft::Shorthands.register :infinite_scroll, trigger: :visible, swap: :after
49
+ Weft::Shorthands.register :live_search, trigger: :input, swap: :fill
50
+ Weft::Shorthands.register :tabs, trigger: :click, swap: :fill
51
+
52
+ # Retry is the odd one out: its value is a URL (the failing component's own GET
53
+ # URL, injected as the :retry_url recovery attr), not a target Class. It ships a
54
+ # concrete hx-target so error components never hand-write htmx — outerHTML-swapping
55
+ # the enclosing .weft-error box replaces the whole error display with the fresh
56
+ # component. Callers override target: for a differently-classed container.
57
+ Weft::Shorthands.register :retry, trigger: :click, swap: :outer_html, target: "closest .weft-error"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Weft
4
+ VERSION = "0.1.0"
5
+ end
data/lib/weft.rb ADDED
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ require "active_support"
6
+ require "active_support/core_ext/integer/time"
7
+ require "active_support/core_ext/string/inflections"
8
+ require "arbre"
9
+
10
+ require "weft/error"
11
+ require "weft/attributes"
12
+ require "weft/dsl/attributes"
13
+ require "weft/dsl/recoveries"
14
+ require "weft/dsl/triggers"
15
+ require "weft/dsl/inclusions"
16
+ require "weft/dsl/updates"
17
+ require "weft/dsl/actions"
18
+ require "weft/dsl/containers"
19
+ require "weft/configuration"
20
+ require "weft/registry"
21
+ require "weft/registry/eligibility"
22
+ require "weft/action"
23
+ require "weft/component"
24
+ require "weft/context"
25
+ require "weft/resolver"
26
+ require "weft/page"
27
+ require "weft/redirect"
28
+ require "weft/router"
29
+ require "weft/version"
30
+
31
+ # Component-oriented hypermedia for Ruby.
32
+ module Weft
33
+ class << self
34
+ attr_writer :logger
35
+
36
+ # Weft's process-wide logger. In standalone mode this defaults to a $stdout
37
+ # Logger at the configured log_level (:info by default) — modeling the
38
+ # unified activity+error stream a 12-factor Rails container emits under
39
+ # RAILS_LOG_TO_STDOUT, so deployments aggregate it like any other web
40
+ # container. When mounted in Rails, set +Weft.logger = Rails.logger+ and
41
+ # this default never applies. Weft.configure applies log_level to it.
42
+ def logger
43
+ @logger ||= Logger.new($stdout)
44
+ end
45
+
46
+ def configuration
47
+ @configuration ||= Configuration.new
48
+ end
49
+
50
+ def registry
51
+ @registry ||= Registry.new
52
+ end
53
+
54
+ def configure
55
+ yield configuration
56
+ apply_configuration
57
+ end
58
+
59
+ # Convenience wrapper for Weft::Redirect.to.
60
+ def redirect(target, **attrs)
61
+ Redirect.to(target, **attrs)
62
+ end
63
+
64
+ # Register a named interaction shorthand. Delegates to Weft::Shorthands.
65
+ #
66
+ # Weft.register_shorthand :tooltip, trigger: :hover, swap: :fill
67
+ def register_shorthand(name, **defaults)
68
+ Shorthands.register(name, **defaults)
69
+ end
70
+
71
+ # Look up a registered shorthand by name. Delegates to Weft::Shorthands.
72
+ def shorthand(name)
73
+ Shorthands.lookup(name)
74
+ end
75
+
76
+ private
77
+
78
+ # Apply side-effects from the current Weft.configuration. Called by
79
+ # Weft.configure after the user block yields. Safe to call multiple
80
+ # times — each apply step is idempotent.
81
+ def apply_configuration
82
+ logger.level = configuration.resolved_log_level
83
+ Router.set(:logging, configuration.router_logging)
84
+ enable_auto_reload! if configuration.auto_reload && !@auto_reload_applied
85
+ apply_static_assets!
86
+ end
87
+
88
+ def enable_auto_reload!
89
+ require "sinatra/reloader"
90
+ Router.register(Sinatra::Reloader)
91
+ configuration.reload_paths.each { |path| Router.also_reload(path) }
92
+ @auto_reload_applied = true
93
+ end
94
+
95
+ def apply_static_assets!
96
+ @mounted_static_bundles ||= Set.new
97
+ configuration.static_assets.each do |name, bundle|
98
+ next if @mounted_static_bundles.include?(name)
99
+
100
+ mount_static_assets_route(bundle[:root], bundle[:from])
101
+ @mounted_static_bundles << name
102
+ end
103
+ end
104
+
105
+ # Mount a Sinatra before-filter that serves files under `from` at the URL
106
+ # prefix `root`. send_file handles content-type/etag/halt; the expand-
107
+ # path containment check rejects path-traversal attempts (e.g.
108
+ # /static/../../etc/passwd resolves outside `from` and returns 404).
109
+ def mount_static_assets_route(root, from)
110
+ expanded_from = File.expand_path(from)
111
+ Router.before "#{root}/*" do
112
+ next unless request.get? || request.head?
113
+
114
+ requested = File.expand_path(params[:splat].first, expanded_from)
115
+ halt 404 unless requested.start_with?("#{expanded_from}/") && File.file?(requested)
116
+
117
+ send_file requested
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ require "weft/shorthands"
124
+ require "weft/defaults"
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: weft
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andy Rusterholz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: arbre
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra-contrib
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: Weft is a rapid web development framework where front-end components
70
+ declare what behaviors are possible, and the back-end routing and request handling
71
+ is derived automatically. No routes. No controllers. Just Weft.
72
+ email:
73
+ - andyrusterholz@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - CHANGELOG.md
79
+ - LICENSE.txt
80
+ - README.md
81
+ - docs/app-patterns.md
82
+ - docs/arbre.md
83
+ - docs/configuration.md
84
+ - docs/dsl.md
85
+ - docs/error-handling.md
86
+ - docs/examples/README.md
87
+ - docs/examples/active-search.md
88
+ - docs/examples/browser-dialogs.md
89
+ - docs/examples/bulk-update.md
90
+ - docs/examples/click-to-edit.md
91
+ - docs/examples/click-to-load.md
92
+ - docs/examples/delete-row.md
93
+ - docs/examples/edit-row.md
94
+ - docs/examples/file-upload.md
95
+ - docs/examples/infinite-scroll.md
96
+ - docs/examples/inline-expansion.md
97
+ - docs/examples/inline-validation.md
98
+ - docs/examples/keyboard-shortcuts.md
99
+ - docs/examples/lazy-loading.md
100
+ - docs/examples/live-ticker.md
101
+ - docs/examples/modal-dialog.md
102
+ - docs/examples/progress-bar.md
103
+ - docs/examples/reset-user-input.md
104
+ - docs/examples/tabs.md
105
+ - docs/examples/tooltip.md
106
+ - docs/examples/updating-other-content.md
107
+ - docs/examples/value-select.md
108
+ - docs/routing.md
109
+ - docs/tutorial.md
110
+ - lib/weft.rb
111
+ - lib/weft/action.rb
112
+ - lib/weft/attributes.rb
113
+ - lib/weft/component.rb
114
+ - lib/weft/configuration.rb
115
+ - lib/weft/context.rb
116
+ - lib/weft/context/interception.rb
117
+ - lib/weft/defaults.rb
118
+ - lib/weft/defaults/error_component.rb
119
+ - lib/weft/defaults/error_page.rb
120
+ - lib/weft/defaults/not_found_component.rb
121
+ - lib/weft/defaults/not_found_page.rb
122
+ - lib/weft/dsl/actions.rb
123
+ - lib/weft/dsl/attributes.rb
124
+ - lib/weft/dsl/containers.rb
125
+ - lib/weft/dsl/inclusions.rb
126
+ - lib/weft/dsl/recoveries.rb
127
+ - lib/weft/dsl/triggers.rb
128
+ - lib/weft/dsl/updates.rb
129
+ - lib/weft/error.rb
130
+ - lib/weft/page.rb
131
+ - lib/weft/redirect.rb
132
+ - lib/weft/registry.rb
133
+ - lib/weft/registry/eligibility.rb
134
+ - lib/weft/resolver.rb
135
+ - lib/weft/router.rb
136
+ - lib/weft/router/actions.rb
137
+ - lib/weft/router/errors.rb
138
+ - lib/weft/router/oob_includes.rb
139
+ - lib/weft/router/streaming.rb
140
+ - lib/weft/shorthands.rb
141
+ - lib/weft/version.rb
142
+ homepage: https://github.com/rusterholz/weft
143
+ licenses:
144
+ - MIT
145
+ metadata:
146
+ homepage_uri: https://github.com/rusterholz/weft
147
+ source_code_uri: https://github.com/rusterholz/weft
148
+ changelog_uri: https://github.com/rusterholz/weft/blob/main/CHANGELOG.md
149
+ rubygems_mfa_required: 'true'
150
+ post_install_message:
151
+ rdoc_options: []
152
+ require_paths:
153
+ - lib
154
+ required_ruby_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: 3.2.0
159
+ required_rubygems_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubygems_version: 3.5.22
166
+ signing_key:
167
+ specification_version: 4
168
+ summary: Component-oriented hypermedia for Ruby.
169
+ test_files: []