funicular 0.5.1 → 0.5.2

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.
data/minitest/ssr_test.rb CHANGED
@@ -32,6 +32,22 @@ class SSRTest < Minitest::Test
32
32
  serialize(el("p", {}, ["a & b <c>"]))
33
33
  end
34
34
 
35
+ def test_marks_empty_slots_and_seams_between_texts
36
+ assert_equal "<div><!----><b></b>a<!---->b<i></i>c</div>",
37
+ serialize(el("div", {}, [nil, el("b"), "a", "b", el("i"), "c"]))
38
+ end
39
+
40
+ def test_raw_text_elements_are_not_marked
41
+ assert_equal "<textarea>ab</textarea>", serialize(el("textarea", {}, [nil, "a", "b"]))
42
+ end
43
+
44
+ def test_doubles_the_leading_newline_the_parser_strips
45
+ assert_equal "<pre>\n\nfoo</pre>", serialize(el("pre", {}, ["\nfoo"]))
46
+ assert_equal "<textarea>\n\nfoo</textarea>", serialize(el("textarea", {}, ["\nfoo"]))
47
+ assert_equal "<pre>foo\n</pre>", serialize(el("pre", {}, ["foo\n"]))
48
+ assert_equal "<div>\nfoo</div>", serialize(el("div", {}, ["\nfoo"]))
49
+ end
50
+
35
51
  def test_escapes_attribute_values
36
52
  assert_equal '<div title="&quot;hi&quot;"></div>',
37
53
  serialize(el("div", { title: '"hi"' }))
data/mrblib/component.rb CHANGED
@@ -418,6 +418,14 @@ module Funicular
418
418
  @suspense_definitions ||= {}
419
419
  end
420
420
 
421
+ # True while patch is re-rendering this component. Callers that would
422
+ # re-enter patch from inside that render (for example a child page that
423
+ # navigates in component_mounted) must take another path, because patch
424
+ # ignores nested calls.
425
+ def updating?
426
+ @updating
427
+ end
428
+
421
429
  # Update state and trigger re-render
422
430
  def patch(new_state)
423
431
  return unless @mounted
@@ -709,8 +717,8 @@ module Funicular
709
717
  end
710
718
 
711
719
  # Recursively bind events for children
712
- if vnode.children && dom_element.children
713
- children = dom_element.children.to_a
720
+ if vnode.children
721
+ children = VDOM.child_nodes(dom_element)
714
722
  vnode.children.each_with_index do |child_vnode, index|
715
723
  if child_vnode.is_a?(VDOM::Element)
716
724
  child_element = children[index]
@@ -748,8 +756,8 @@ module Funicular
748
756
  refs_map[ref_name] = dom_element
749
757
  end
750
758
 
751
- if vnode.children && dom_element.children
752
- children = dom_element.children.to_a
759
+ if vnode.children
760
+ children = VDOM.child_nodes(dom_element)
753
761
  vnode.children.each_with_index do |child_vnode, index|
754
762
  if child_vnode.is_a?(VDOM::Element)
755
763
  child_element = children[index]
@@ -810,6 +818,20 @@ module Funicular
810
818
  __view__.component(component_class, props, &block)
811
819
  end
812
820
 
821
+ def outlet
822
+ router = @runtime&.router
823
+ depth = router&.current_layouts&.index(self.class)
824
+ return unless router && depth
825
+
826
+ nested = router.current_layouts[depth + 1]
827
+ return component(nested, preserve: true, __route__: router.current_path) if nested
828
+
829
+ route = router.current_route
830
+ return unless route
831
+
832
+ component(route[0], route[1])
833
+ end
834
+
813
835
  def form_for(model_key, options = {}, &block)
814
836
  build_form_for(__view__, model_key, options, &block)
815
837
  end
@@ -1024,9 +1046,22 @@ module Funicular
1024
1046
  return unless vnode.is_a?(VDOM::Element)
1025
1047
  return unless dom_element
1026
1048
 
1027
- dom_children = dom_element.children.to_a
1049
+ dom_children = VDOM.child_nodes(dom_element)
1050
+ skipped = 0
1051
+ previous_text = false
1028
1052
  vnode.children.each_with_index do |child, index|
1029
- child_dom = dom_children[index]
1053
+ text = VDOM.text(child)
1054
+ child_dom = dom_children[index + skipped]
1055
+ if text && child_dom && child_dom[:nodeType] == 8
1056
+ if text.empty?
1057
+ dom_element.replaceChild(dom_element[:ownerDocument].createTextNode(''), child_dom)
1058
+ elsif previous_text
1059
+ dom_element.removeChild(child_dom)
1060
+ skipped += 1
1061
+ child_dom = dom_children[index + skipped]
1062
+ end
1063
+ end
1064
+ previous_text = !text.to_s.empty?
1030
1065
  next unless child_dom
1031
1066
 
1032
1067
  if child.is_a?(VDOM::Component)
data/mrblib/differ.rb CHANGED
@@ -29,6 +29,8 @@ module Funicular
29
29
  else
30
30
  if old_node == new_node
31
31
  []
32
+ elsif old_node.is_a?(String) && new_node.is_a?(String)
33
+ [[:text, new_node]]
32
34
  else
33
35
  [[:replace, new_node, old_node]]
34
36
  end
@@ -38,11 +40,7 @@ module Funicular
38
40
  private
39
41
 
40
42
  def self.diff_text(old_node, new_node)
41
- if old_node.content != new_node.content
42
- [[:replace, new_node, old_node]]
43
- else
44
- []
45
- end
43
+ old_node.content == new_node.content ? [] : [[:text, new_node.content]]
46
44
  end
47
45
 
48
46
  def self.diff_element(old_node, new_node)
@@ -158,10 +156,10 @@ module Funicular
158
156
  # "Loading..." placeholder that never disappeared once the keyed
159
157
  # list it was replaced by arrived. Raw String children are text
160
158
  # nodes in the DOM (normalize_children keeps them as Strings), so
161
- # they must be collected here as well; only nil slots are skipped.
159
+ # they must be collected here as well. normalize_children never
160
+ # leaves nil in the list, so every entry is a real DOM node.
162
161
  removes = [] #: Array[[Integer, child_t]]
163
162
  old_children.each_with_index do |old_child, old_index|
164
- next if old_child.nil?
165
163
  next if matched_old_indices[old_index]
166
164
  removes << [old_index, old_child]
167
165
  has_change = true
@@ -18,6 +18,13 @@ module Funicular
18
18
  # Props that must never be emitted as HTML attributes.
19
19
  SKIP_PROPS = %i[ref key]
20
20
 
21
+ RAW_TEXT_ELEMENTS = %w[textarea title style]
22
+
23
+ # The HTML parser drops one newline right after the start tag of these
24
+ # elements. Emit an extra newline so the text node the client VDOM
25
+ # expects survives parsing with its content intact.
26
+ NEWLINE_STRIPPING_ELEMENTS = %w[pre textarea listing]
27
+
21
28
  def self.serialize(vnode, runtime = nil)
22
29
  new(runtime).render(vnode)
23
30
  end
@@ -50,27 +57,33 @@ module Funicular
50
57
  tag = element.tag
51
58
  attrs = serialize_props(element.props)
52
59
 
53
- if VOID_ELEMENTS.include?(tag.downcase)
60
+ lower = tag.downcase
61
+ if VOID_ELEMENTS.include?(lower)
54
62
  "<#{tag}#{attrs}>"
55
63
  else
56
- "<#{tag}#{attrs}>#{render_children(element.children)}</#{tag}>"
64
+ inner = render_children(element.children, !RAW_TEXT_ELEMENTS.include?(lower))
65
+ inner = "\n#{inner}" if NEWLINE_STRIPPING_ELEMENTS.include?(lower) && inner.start_with?("\n")
66
+ "<#{tag}#{attrs}>#{inner}</#{tag}>"
57
67
  end
58
68
  end
59
69
 
60
- def render_children(children)
61
- parts = [] #: Array[String]
62
- children.each do |child|
63
- if child.is_a?(VNode)
64
- parts << render(child)
65
- elsif child.is_a?(String)
66
- parts << escape_html(child)
67
- elsif child.is_a?(Array)
68
- parts << render_children(child)
69
- elsif !child.nil?
70
- parts << escape_html(child.to_s)
70
+ # Element#children is flattened by normalize_children, so nested arrays
71
+ # need no handling here.
72
+ def render_children(children, marks)
73
+ previous_text = false
74
+ children.map do |child|
75
+ text = VDOM.text(child)
76
+ html = if text
77
+ escape_html(text)
78
+ elsif child.is_a?(VNode)
79
+ render(child)
80
+ else
81
+ ''
71
82
  end
72
- end
73
- parts.join
83
+ html = "<!---->#{html}" if marks && text && (text.empty? || previous_text)
84
+ previous_text = !text.to_s.empty?
85
+ html
86
+ end.join
74
87
  end
75
88
 
76
89
  def render_text(text)
data/mrblib/patcher.rb CHANGED
@@ -24,6 +24,8 @@ module Funicular
24
24
  # Props patches only make sense for Element nodes (text nodes have
25
25
  # no attributes); narrow before delegating.
26
26
  update_props(element, patch[1]) if element.is_a?(JS::Element)
27
+ when :text
28
+ element[:nodeValue] = patch[1].to_s
27
29
  when :update_and_rebind
28
30
  instance, internal_patches, new_vdom = patch[1], patch[2], patch[3]
29
31
  old_dom_element = instance.dom_element
@@ -85,7 +87,7 @@ module Funicular
85
87
  ops = patch[1]
86
88
  removes = patch[2]
87
89
 
88
- snapshot = child_nodes_array(element)
90
+ snapshot = VDOM.child_nodes(element)
89
91
 
90
92
  # Phase 1: removes (descending old_index)
91
93
  sorted_removes = removes.sort { |a, b| b[0] <=> a[0] }
@@ -146,7 +148,7 @@ module Funicular
146
148
  when Integer
147
149
  child_index = patch[0]
148
150
  child_patches = patch[1]
149
- children = child_nodes_array(element)
151
+ children = VDOM.child_nodes(element)
150
152
  child_element = children[child_index]
151
153
  if child_element.nil?
152
154
  # No existing child at this index - we need to create new elements
@@ -164,7 +166,7 @@ module Funicular
164
166
  end
165
167
  elsif child_element.is_a?(JS::Object)
166
168
  # Recurse into the child node. apply handles both Element and
167
- # text Node cases (the latter only meaningfully via :replace).
169
+ # text Node cases (the latter via :text or :replace).
168
170
  apply(child_element, child_patches)
169
171
  end
170
172
  end
@@ -174,14 +176,6 @@ module Funicular
174
176
 
175
177
  private
176
178
 
177
- # childNodes (not children) so that text nodes are included. Each call
178
- # crosses the wasm boundary once per child, so callers should read it
179
- # once and work on the returned array.
180
- def child_nodes_array(element)
181
- child_nodes = element[:childNodes]
182
- child_nodes.is_a?(JS::Object) ? child_nodes.to_a : [] #: Array[untyped]
183
- end
184
-
185
179
  def unmount_component(vnode)
186
180
  return unless vnode.is_a?(VDOM::Component) && vnode.instance
187
181
  vnode.instance.unmount
data/mrblib/router.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Funicular
2
2
  class Router
3
- attr_reader :routes, :current_component, :current_path, :url_helpers, :route_helpers
3
+ attr_reader :routes, :current_path, :url_helpers, :route_helpers, :current_route, :current_layouts
4
4
 
5
5
  def initialize(container)
6
6
  @container = container
@@ -8,6 +8,10 @@ module Funicular
8
8
  @default_route = nil
9
9
  @current_component = nil
10
10
  @current_path = nil
11
+ @current_route = nil
12
+ @current_layouts = []
13
+ @layout_stack = [] #: Array[singleton(Component)]
14
+ @layout_root = nil
11
15
  @popstate_callback_id = nil
12
16
  @beforeunload_callback_id = nil
13
17
  @url_helpers = Module.new
@@ -47,6 +51,27 @@ module Funicular
47
51
  @default_route = path
48
52
  end
49
53
 
54
+ def layout(component_class)
55
+ @layout_stack.push(component_class)
56
+ yield
57
+ ensure
58
+ @layout_stack.pop
59
+ end
60
+
61
+ def current_component
62
+ layout_root = @layout_root
63
+ route = @current_route
64
+ return @current_component unless layout_root && route
65
+
66
+ instance = layout_root #: Component?
67
+ chain = (@current_layouts[1..-1] || []) + [route[0]]
68
+ chain.each do |klass|
69
+ return nil unless instance
70
+ instance = find_component_instance(instance.vdom, klass)
71
+ end
72
+ instance
73
+ end
74
+
50
75
  # Resolve a path to [component_class, params] without any DOM/JS work.
51
76
  # Public entry point used by server-side rendering.
52
77
  def match(path)
@@ -91,7 +116,7 @@ module Funicular
91
116
  # a guard is active. sync: the decision must be made on the JS
92
117
  # event dispatch stack, so the guard must not suspend.
93
118
  @beforeunload_callback_id = JS.global.addEventListener('beforeunload', sync: true) do |event|
94
- if @current_component&.navigation_guard
119
+ if current_component&.navigation_guard
95
120
  event.preventDefault
96
121
  event[:returnValue] = ''
97
122
  end
@@ -136,7 +161,7 @@ module Funicular
136
161
  # allowed; a String from the guard prompts the user via
137
162
  # Funicular.confirm. True when no component or no guard.
138
163
  def leave_allowed?
139
- message = @current_component&.navigation_guard
164
+ message = current_component&.navigation_guard
140
165
  return true unless message
141
166
  Funicular.confirm(message)
142
167
  end
@@ -160,16 +185,19 @@ module Funicular
160
185
  @hydrate_initial = false
161
186
 
162
187
  # Find matching route
163
- component_class, params = find_route(path)
188
+ route, params = match_route(path)
164
189
 
165
- unless component_class
190
+ unless route
166
191
  # Maybe render a 404 component?
167
192
  return
168
193
  end
194
+ component_class = route[:component]
169
195
 
170
196
  # Don't remount if already on this path
171
197
  return if @current_path == path
172
198
 
199
+ return render_with_layouts(route, params, path, hydrate_now) unless route[:layouts].empty?
200
+
173
201
  # Unmount current component
174
202
  unmount_current_component
175
203
 
@@ -198,10 +226,56 @@ module Funicular
198
226
  @current_component.mount(@container)
199
227
  end
200
228
 
229
+ def render_with_layouts(route, params, path, hydrate_now)
230
+ if hydrate_now && Funicular.first_element_child(@container)
231
+ puts '[Funicular] Layouts do not support hydration; rendering fresh.'
232
+ @container[:innerHTML] = ''
233
+ end
234
+
235
+ layouts = route[:layouts]
236
+ layout_root = @layout_root
237
+ # Reuse the mounted chain only under the same outermost layout, and
238
+ # only when that root is not mid-render: Component#patch drops nested
239
+ # calls, so a page that navigates from component_mounted during a
240
+ # re-render would otherwise change the URL and leave the old page in
241
+ # the DOM. Remounting the chain matches what bare routes do.
242
+ reusable = layout_root && layout_root.mounted && !layout_root.updating? &&
243
+ @current_layouts.first == layouts.first
244
+ unmount_current_component unless reusable
245
+
246
+ @current_path = path
247
+ @current_route = [route[:component], params]
248
+ @current_layouts = layouts
249
+
250
+ if layout_root && reusable
251
+ layout_root.patch(__route__: path)
252
+ else
253
+ layout_root = layouts.first.new
254
+ layout_root.runtime = @runtime
255
+ @layout_root = layout_root
256
+ layout_root.mount(@container)
257
+ end
258
+ end
259
+
260
+ def find_component_instance(vnode, component_class)
261
+ return unless vnode.is_a?(VDOM::Element) || vnode.is_a?(VDOM::Component)
262
+ return vnode.instance if vnode.is_a?(VDOM::Component) && vnode.component_class == component_class
263
+
264
+ vnode.children.each do |child|
265
+ found = find_component_instance(child, component_class)
266
+ return found if found
267
+ end
268
+ nil
269
+ end
270
+
201
271
  # Unmount current component
202
272
  def unmount_current_component
203
273
  @current_component&.unmount
204
274
  @current_component = nil
275
+ @layout_root&.unmount
276
+ @layout_root = nil
277
+ @current_layouts = []
278
+ @current_route = nil
205
279
  @current_path = nil
206
280
  end
207
281
 
@@ -215,7 +289,8 @@ module Funicular
215
289
  component: component_class,
216
290
  name: name,
217
291
  pattern_segments: pattern_segments,
218
- constraints: constraints || {}
292
+ constraints: constraints || {},
293
+ layouts: @layout_stack.dup
219
294
  }
220
295
  # @type var route: Funicular::route_definition_t
221
296
  @routes << route
@@ -273,6 +348,11 @@ module Funicular
273
348
  end
274
349
 
275
350
  def find_route(path)
351
+ route, params = match_route(path)
352
+ [route && route[:component], params]
353
+ end
354
+
355
+ def match_route(path)
276
356
  path_segments = path.split('/').reject { |s| s.empty? }
277
357
  params = {} #: Hash[Symbol, untyped]
278
358
 
@@ -302,7 +382,7 @@ module Funicular
302
382
  end
303
383
  end
304
384
 
305
- return [route[:component], params] if match
385
+ return [route, params] if match
306
386
  end
307
387
 
308
388
  [nil, params] # No route found
data/mrblib/vdom.rb CHANGED
@@ -39,6 +39,16 @@ module Funicular
39
39
  true
40
40
  end
41
41
 
42
+ def self.text(child)
43
+ return child if child.is_a?(String)
44
+ child.content.to_s if child.is_a?(Text)
45
+ end
46
+
47
+ def self.child_nodes(element)
48
+ nodes = element[:childNodes]
49
+ nodes.is_a?(JS::Object) ? nodes.to_a : [] #: Array[untyped]
50
+ end
51
+
42
52
  def self.event_attribute?(name)
43
53
  name.to_s.downcase.start_with?('on')
44
54
  end
@@ -120,8 +130,8 @@ module Funicular
120
130
  # Recursively normalize nested arrays
121
131
  # @type var child: Array[Funicular::VDOM::child_t]
122
132
  result.concat(normalize_children(child))
123
- when nil
124
- # Skip nil values
133
+ when nil, false
134
+ result << ''
125
135
  else
126
136
  # Convert other types to strings
127
137
  result << child.to_s
data/mrblib/version.rb CHANGED
@@ -7,5 +7,5 @@
7
7
  # Keep this file free of any dependency: it must evaluate standalone in
8
8
  # both PicoRuby and CRuby, regardless of load order.
9
9
  module Funicular
10
- VERSION = '0.5.1'
10
+ VERSION = '0.5.2'
11
11
  end
data/sig/component.rbs CHANGED
@@ -61,6 +61,7 @@ module Funicular
61
61
  def render_suspense: (Symbol name, fallback: untyped, ?error: untyped) { (ResourceAccessor) -> untyped } -> untyped
62
62
 
63
63
  def navigation_guard: () -> String?
64
+ def updating?: () -> bool
64
65
  def patch: (Hash[Symbol, untyped] new_state) -> void
65
66
  def mount: (JS::Element container) -> void
66
67
  def hydrate: (JS::Element dom_element) -> void
@@ -75,6 +76,7 @@ module Funicular
75
76
  def __view__: () -> ViewContext
76
77
  def routes: () -> untyped
77
78
  def component: (singleton(Component) component_class, ?Hash[Symbol, untyped] props) ?{ () -> untyped } -> VDOM::Component
79
+ def outlet: () -> VDOM::Component?
78
80
  def form_for: (Symbol model_key, ?Hash[Symbol, untyped] options) { (FormBuilder) -> void } -> VDOM::Element
79
81
  def link_to: (String path, **untyped options) ?{ -> untyped } -> VDOM::Element
80
82
  def button_to: (String path, ?method: Symbol, **untyped options) ?{ -> untyped } -> VDOM::Element
@@ -3,6 +3,8 @@ module Funicular
3
3
  class HTMLSerializer
4
4
  VOID_ELEMENTS: Array[String]
5
5
  SKIP_PROPS: Array[Symbol]
6
+ RAW_TEXT_ELEMENTS: Array[String]
7
+ NEWLINE_STRIPPING_ELEMENTS: Array[String]
6
8
 
7
9
  def self.serialize: (VNode? vnode, ?Runtime? runtime) -> String
8
10
  def initialize: (?Runtime? runtime) -> void
@@ -10,7 +12,7 @@ module Funicular
10
12
 
11
13
  private
12
14
  def render_element: (Element element) -> String
13
- def render_children: (Array[child_t] children) -> String
15
+ def render_children: (Array[child_t] children, bool marks) -> String
14
16
  def render_text: (Text text) -> String
15
17
  def render_component: (Component component_vnode) -> String
16
18
  def serialize_props: (Hash[Symbol, untyped] props) -> String
data/sig/patcher.rbs CHANGED
@@ -9,7 +9,6 @@ module Funicular
9
9
  def apply: (JS::Object element, Array[patch_t] patches) -> JS::Object
10
10
 
11
11
  # Accepts JS::Object since callers narrow via is_a?(JS::Element) at runtime
12
- private def child_nodes_array: (JS::Object element) -> Array[untyped]
13
12
  private def update_props: (JS::Object element, Hash[Symbol, String?] props_patch) -> void
14
13
  private def create_element: (untyped vnode) -> JS::Object
15
14
  private def unmount_component: (VDOM::VNode vnode) -> void
data/sig/router.rbs CHANGED
@@ -1,10 +1,11 @@
1
1
  module Funicular
2
2
  type route_constraints_t = Hash[Symbol, Regexp]
3
- type route_definition_t = { method: Symbol, path: String, component: singleton(Component), name: String?, pattern_segments: Array[String], constraints: route_constraints_t }
3
+ type route_definition_t = { method: Symbol, path: String, component: singleton(Component), name: String?, pattern_segments: Array[String], constraints: route_constraints_t, layouts: Array[singleton(Component)] }
4
4
 
5
5
  class Router
6
6
  attr_reader routes: Array[route_definition_t]
7
- attr_reader current_component: Component?
7
+ attr_reader current_route: [singleton(Component), Hash[Symbol, untyped]]?
8
+ attr_reader current_layouts: Array[singleton(Component)]
8
9
  attr_reader current_path: String?
9
10
  attr_reader url_helpers: Module
10
11
  attr_reader route_helpers: untyped
@@ -17,6 +18,8 @@ module Funicular
17
18
  def delete: (String path, to: singleton(Component), ?as: String?, ?constraints: route_constraints_t?) -> void
18
19
  def add_route: (String path, singleton(Component) component_class, ?as: String?, ?constraints: route_constraints_t?) -> void
19
20
  def set_default: (String path) -> void
21
+ def layout: (singleton(Component) component_class) { () -> void } -> void
22
+ def current_component: () -> Component?
20
23
  def match: (String path) -> [singleton(Component)?, Hash[Symbol, untyped]]
21
24
  def start: (?hydrate: bool) -> void
22
25
  def stop: () -> void
@@ -28,7 +31,10 @@ module Funicular
28
31
  private def generate_url_helper: (String name, String path_pattern) -> void
29
32
  private def extract_param_names: (String path_pattern) -> Array[Symbol]
30
33
  private def handle_route_change: () -> void
34
+ private def render_with_layouts: (route_definition_t route, Hash[Symbol, untyped] params, String path, bool? hydrate_now) -> void
35
+ private def find_component_instance: (untyped vnode, singleton(Component) component_class) -> Component?
31
36
  private def unmount_current_component: () -> void
32
37
  private def find_route: (String path) -> [singleton(Component) | nil, Hash[Symbol, untyped]]
38
+ private def match_route: (String path) -> [route_definition_t?, Hash[Symbol, untyped]]
33
39
  end
34
40
  end
data/sig/vdom.rbs CHANGED
@@ -7,6 +7,8 @@ module Funicular
7
7
  SCRIPTING_ELEMENTS: Array[String]
8
8
 
9
9
  def self.valid_tag_name?: (untyped name) -> bool
10
+ def self.text: (untyped child) -> String?
11
+ def self.child_nodes: (untyped element) -> Array[untyped]
10
12
  def self.valid_attribute_name?: (untyped name) -> bool
11
13
  def self.event_attribute?: (untyped name) -> bool
12
14
  def self.unsafe_url?: (untyped name, untyped value) -> bool
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: funicular
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - HASUMI Hitoshi
@@ -187,7 +187,7 @@ homepage: https://github.com/picoruby/funicular
187
187
  licenses:
188
188
  - MIT
189
189
  metadata:
190
- homepage_uri: https://picoruby.org/funicular-getting-started
190
+ homepage_uri: https://picoruby.org/funicular
191
191
  source_code_uri: https://github.com/picoruby/funicular
192
192
  changelog_uri: https://github.com/picoruby/funicular/blob/master/CHANGELOG.md
193
193
  post_install_message: |2+
@@ -226,6 +226,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
226
  requirements: []
227
227
  rubygems_version: 4.0.20
228
228
  specification_version: 4
229
- summary: Rails plugin for client-side Ruby development with mruby
229
+ summary: Rails plugin for client-side Ruby development with PicoRuby
230
230
  test_files: []
231
231
  ...