poetry-core 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72998473fe858b7965080423352da67d02f8cc6c35caf3bf53369f590808894d
4
- data.tar.gz: ee168d90ba65226c1016ff00c857e782136a388d2d29cf19d4f0a35d6f330af1
3
+ metadata.gz: f9fa980bdf8c3d65b51511142615257e850e3ce355fda679dec434aa34e0e3d9
4
+ data.tar.gz: fb7a6d93fec947bcaa4934f70163679a0c0c196e54985012e821e6d1840b614f
5
5
  SHA512:
6
- metadata.gz: 9c0a44e06ddadad7ff598f256ffc0134da027d3fb00e528228bda28f901a9d72758d29ed0aab0055b196df1d8fa8453aaa644f37301bde3ad36fb6a12c60290e
7
- data.tar.gz: 56b8888668d81cc38c4ff5c78240fc57fd2ecc8370c22f59980055ada7c7d9d4a5dd5dcdb0ba5f5d79bba0f05a501956a35201c5a3f5d2b2aa537a0ebeb25eef
6
+ metadata.gz: aab549c214fc3ce8312692c3cca3cbf2daf0ee551fed9b9514a02110fe0d31542a5f8b8a972682bf0a11e53fcb99ef8f378453284151e6078ce532f83faf23a7
7
+ data.tar.gz: 8137bf971f7ececd840a190c61deba49fc29408fab30d9696b5f8ce7fd3b934db22585f3070fb0effff09532d1c0992b02831d4590b78cb3337e6aa2cb83a563
data/.gitattributes ADDED
@@ -0,0 +1,5 @@
1
+ # GitHub language stats (Linguist): the vendored floating-ui dists and the
2
+ # JS test suite are not this gem's source; without this the repo reads as
3
+ # JavaScript instead of Ruby.
4
+ app/javascript/poetry/core/vendor/** linguist-vendored
5
+ test/javascript/** linguist-vendored
@@ -27,7 +27,8 @@ const isApplePlatform = () => /iP(hone|ad|od)/.test(navigator.userAgent) ||
27
27
 
28
28
  const EDITABLE = {
29
29
  date: ["year", "month", "day"],
30
- time: ["hour", "minute", "second", "dayPeriod"]
30
+ time: ["hour", "minute", "second", "dayPeriod"],
31
+ datetime: ["year", "month", "day", "hour", "minute", "second", "dayPeriod"]
31
32
  }
32
33
 
33
34
  export default class DateFieldController extends Controller {
@@ -109,8 +110,15 @@ export default class DateFieldController extends Controller {
109
110
 
110
111
  // --- build ---
111
112
 
113
+ // The native input's type is the contract: date, time, or (datetime-local)
114
+ // both runs in one row, joined by the locale's own separator.
112
115
  get #kind() {
113
- return this.inputTarget.type === "time" ? "time" : "date"
116
+ const type = this.inputTarget.type
117
+
118
+ if (type === "time") return "time"
119
+ if (type === "datetime-local") return "datetime"
120
+
121
+ return "date"
114
122
  }
115
123
 
116
124
  #resolvedHourCycle() {
@@ -122,13 +130,15 @@ export default class DateFieldController extends Controller {
122
130
  }
123
131
 
124
132
  #formatterOptions() {
125
- if (this.#kind === "date") return { year: "numeric", month: "numeric", day: "numeric" }
133
+ const date = { year: "numeric", month: "numeric", day: "numeric" }
134
+
135
+ if (this.#kind === "date") return date
126
136
 
127
137
  const options = { hour: "numeric", minute: "2-digit", hourCycle: this.#value.hourCycle }
128
138
 
129
139
  if (this.secondsValue) options.second = "2-digit"
130
140
 
131
- return options
141
+ return this.#kind === "datetime" ? { ...date, ...options } : options
132
142
  }
133
143
 
134
144
  #buildSegments() {
@@ -145,7 +155,7 @@ export default class DateFieldController extends Controller {
145
155
 
146
156
  // RTL correctness: the time run is wrapped in LRI/PDI isolates so
147
157
  // minute:hour never renders reversed (harmless in LTR).
148
- if (this.#kind === "time") fragment.appendChild(this.#literal("⁦"))
158
+ if (this.#kind !== "date") fragment.appendChild(this.#literal("⁦"))
149
159
 
150
160
  for (const part of parts) {
151
161
  const type = part.type === "dayperiod" ? "dayPeriod" : part.type
@@ -166,7 +176,7 @@ export default class DateFieldController extends Controller {
166
176
  }
167
177
  }
168
178
 
169
- if (this.#kind === "time") fragment.appendChild(this.#literal("⁩"))
179
+ if (this.#kind !== "date") fragment.appendChild(this.#literal("⁩"))
170
180
 
171
181
  this.groupTarget.replaceChildren(fragment)
172
182
  this.#labelSegments()
@@ -268,7 +278,7 @@ export default class DateFieldController extends Controller {
268
278
  }
269
279
 
270
280
  #resolveDayPeriodNames() {
271
- if (this.#kind !== "time" || !this.#value.twelveHour) return
281
+ if (this.#kind === "date" || !this.#value.twelveHour) return
272
282
 
273
283
  const formatter = new Intl.DateTimeFormat(this.#locale(), { hour: "numeric", hourCycle: this.#value.hourCycle })
274
284
  const nameAt = (hour) =>
@@ -283,16 +293,18 @@ export default class DateFieldController extends Controller {
283
293
  const raw = this.inputTarget.value
284
294
 
285
295
  if (this.#kind === "date") this.#value.setFromISODate(raw)
286
- else this.#value.setFromISOTime(raw)
296
+ else if (this.#kind === "time") this.#value.setFromISOTime(raw)
297
+ else this.#value.setFromISODateTime(raw)
287
298
  }
288
299
 
289
300
  #placeholderFor(type) {
290
301
  const seed = new IncompleteDate(this.#value.hourCycle)
291
302
  const iso = this.placeholderValue
292
303
 
293
- if (this.#kind === "date") {
294
- if (!seed.setFromISODate(iso)) { seed.year = 2020; seed.month = 1; seed.day = 1 }
295
- } else if (!seed.setFromISOTime(iso)) {
304
+ const [datePart, timePart] = this.#kind === "datetime" ? String(iso ?? "").split("T") : [iso, iso]
305
+
306
+ if (this.#kind !== "time" && !seed.setFromISODate(datePart)) { seed.year = 2020; seed.month = 1; seed.day = 1 }
307
+ if (this.#kind !== "date" && !seed.setFromISOTime(timePart)) {
296
308
  seed.setFromH23(12)
297
309
  seed.minute = 0
298
310
  seed.second = 0
@@ -542,7 +554,7 @@ export default class DateFieldController extends Controller {
542
554
  }
543
555
 
544
556
  #commitIfComplete() {
545
- if (this.#kind === "date" && this.#value.isComplete(["year", "month", "day"]) &&
557
+ if (this.#kind !== "time" && this.#value.isComplete(["year", "month", "day"]) &&
546
558
  !this.#value.isValidDate()) return // Feb 31 mid-edit: hold the wire
547
559
 
548
560
  this.#syncInput()
@@ -551,7 +563,9 @@ export default class DateFieldController extends Controller {
551
563
  #syncInput() {
552
564
  const iso = this.#kind === "date"
553
565
  ? this.#value.toISODate()
554
- : this.#value.toISOTime(this.secondsValue)
566
+ : this.#kind === "time"
567
+ ? this.#value.toISOTime(this.secondsValue)
568
+ : this.#value.toISODateTime(this.secondsValue)
555
569
  const next = iso ?? ""
556
570
 
557
571
  if (this.inputTarget.value === next) return
@@ -289,6 +289,35 @@ export class IncompleteDate {
289
289
  this.second = match[3] === undefined ? null : Number(match[3])
290
290
  return true
291
291
  }
292
+
293
+ /**
294
+ * Fills every field from "YYYY-MM-DDTHH:MM(:SS)" - the datetime-local
295
+ * wire format (no zone, local wall time).
296
+ *
297
+ * @param {string | null} iso
298
+ * @returns {boolean} false (nothing touched) unless both halves parse
299
+ */
300
+ setFromISODateTime(iso) {
301
+ // A fractional-seconds tail is tolerated (some engines normalize the
302
+ // value with one) and dropped: the field edits whole seconds.
303
+ const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}(?::\d{2})?)(?:\.\d+)?$/.exec(iso ?? "")
304
+
305
+ if (!match) return false
306
+
307
+ return this.setFromISODate(match[1]) && this.setFromISOTime(match[2])
308
+ }
309
+
310
+ /**
311
+ * @param {boolean} [withSeconds=false]
312
+ * @returns {string | null} "YYYY-MM-DDTHH:MM(:SS)", or null while either
313
+ * half is incomplete
314
+ */
315
+ toISODateTime(withSeconds = false) {
316
+ const date = this.toISODate()
317
+ const time = this.toISOTime(withSeconds)
318
+
319
+ return date && time ? `${date}T${time}` : null
320
+ }
292
321
  }
293
322
 
294
323
  function clampToLimits(value, min, max) {
@@ -796,13 +796,13 @@ module Poetry
796
796
  end
797
797
  end
798
798
 
799
- def walk_prism(node, &block)
799
+ def walk_prism(node, &)
800
800
  return unless node
801
801
 
802
802
  yield node
803
803
  return unless node.respond_to?(:compact_child_nodes)
804
804
 
805
- node.compact_child_nodes.each { |child| walk_prism(child, &block) }
805
+ node.compact_child_nodes.each { |child| walk_prism(child, &) }
806
806
  end
807
807
 
808
808
  # The block seam of a setter, both directions. A yieldless
@@ -1262,11 +1262,11 @@ module Poetry
1262
1262
  joined.empty? ? nil : joined
1263
1263
  end
1264
1264
 
1265
- def walk(node, &block)
1265
+ def walk(node, &)
1266
1266
  return unless node
1267
1267
 
1268
1268
  yield node
1269
- node.child_nodes.compact.each { |child| walk(child, &block) } if node.respond_to?(:child_nodes)
1269
+ node.child_nodes.compact.each { |child| walk(child, &) } if node.respond_to?(:child_nodes)
1270
1270
  end
1271
1271
  end
1272
1272
 
@@ -75,9 +75,9 @@ module Poetry
75
75
  "the herb gem is required for template class extraction - add `gem \"herb\"` to your Gemfile"
76
76
  end
77
77
 
78
- def walk(node, &block)
78
+ def walk(node, &)
79
79
  yield node
80
- node.child_nodes.compact.each { |child| walk(child, &block) } if node.respond_to?(:child_nodes)
80
+ node.child_nodes.compact.each { |child| walk(child, &) } if node.respond_to?(:child_nodes)
81
81
  end
82
82
 
83
83
  def class_attribute?(attribute)
@@ -408,9 +408,9 @@ module Poetry
408
408
  end
409
409
  end
410
410
 
411
- def each_table(node, &block)
411
+ def each_table(node, &)
412
412
  yield node if node.element? && node.tag == "table"
413
- node.children.each { |child| each_table(child, &block) }
413
+ node.children.each { |child| each_table(child, &) }
414
414
  end
415
415
 
416
416
  def collect_badge_variants(node, variants)
@@ -15,10 +15,16 @@ module Poetry
15
15
  #
16
16
  # @api private
17
17
  class SkillText < LlmsText
18
- def initialize(registry:, families:, charts_registry: nil)
18
+ # @param registry [Registry] the component registry the skill describes
19
+ # @param families [Hash{String => Array<String>}] family name => component names
20
+ # @param charts_registry [Registry, nil] the charts registry, when the gem is present
21
+ # @param builder_family [String] the family whose reference carries the registry's
22
+ # form_builder section (rules, method summaries, the f.input as: vocabulary)
23
+ def initialize(registry:, families:, charts_registry: nil, builder_family: "forms")
19
24
  super(registry: registry)
20
25
  @families = families
21
26
  @charts_registry = charts_registry
27
+ @builder_family = builder_family
22
28
  end
23
29
 
24
30
  # The installable file map, paths relative to .claude/skills/poetry/.
@@ -250,9 +256,32 @@ module Poetry
250
256
  not suggestions. Options are keywords; content is the block.
251
257
 
252
258
  #{sections(paths)}
259
+ #{builder_reference if family == @builder_family}
253
260
  MD
254
261
  end
255
262
 
263
+ # The registry's form_builder section rides the family that hosts the
264
+ # form controls: the builder's RULE lines, its method summaries, and
265
+ # the f.input as: vocabulary. A registry without one (charts, a host
266
+ # without the UI gem) contributes nothing.
267
+ def builder_reference
268
+ section = @registry.respond_to?(:form_builder) ? @registry.form_builder : nil
269
+ return "" unless section.is_a?(Hash) && section.any?
270
+
271
+ out = ["## Form builder (model-bound forms)", "",
272
+ "Inside `form_with(model:, builder:)` forms the builder derives label, hint, error, " \
273
+ "and aria from the model; these `RULE` lines bind there."]
274
+ rules = Array(section["rules"])
275
+ out << "" << rules.map { |rule| "- RULE: #{rule}" }.join("\n") if rules.any?
276
+ methods = section["methods"]
277
+ if methods.is_a?(Hash) && methods.any?
278
+ out << "" << "Methods:" << "" << methods.map { |name, summary| "- `#{name}` - #{summary}" }.join("\n")
279
+ end
280
+ types = Array(section["input_types"])
281
+ out << "" << "`f.input` `as:` values: #{types.join(", ")}" if types.any?
282
+ out.join("\n")
283
+ end
284
+
256
285
  # The blocks reference inlines every block's full source - the block
257
286
  # WITH source is the load-bearing agent path.
258
287
  def blocks_reference
@@ -86,7 +86,6 @@ module Poetry
86
86
  # while other data attributes are merged normally.
87
87
  #
88
88
  # @param hashes [Array<Hash>] One or more stimulus data hashes
89
- # @param block [Proc] Optional block passed to Hash#merge for custom merge logic
90
89
  # @yield [key, existing, incoming] Hash#merge's conflict resolver for
91
90
  # every key other than controller and action
92
91
  # @yieldreturn [Object] the value to keep
@@ -97,12 +96,12 @@ module Poetry
97
96
  # { controller: "dropdown tooltip", target_value: "main" }
98
97
  # )
99
98
  # # => { controller: "dropdown tooltip", action: "click->dropdown#toggle", target_value: "main" }
100
- def merge(*hashes, &block)
99
+ def merge(*hashes, &)
101
100
  hashes.flatten.compact.each_with_object({}.with_indifferent_access) do |hash, result|
102
101
  hash = hash.deep_dup.with_indifferent_access
103
102
  result[:controller] = merge_controllers(result[:controller], hash.delete(:controller))
104
103
  result[:action] = merge_actions(result[:action], hash.delete(:action))
105
- result.merge!(hash, &block)
104
+ result.merge!(hash, &)
106
105
  end
107
106
  end
108
107
 
@@ -4,6 +4,6 @@ module Poetry
4
4
  module Core
5
5
  # The gem version. Every gem in the family carries the same version
6
6
  # and pins poetry-core to it exactly.
7
- VERSION = "0.0.2"
7
+ VERSION = "0.0.3"
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: poetry-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Solt
@@ -73,6 +73,7 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - ".gitattributes"
76
77
  - CHANGELOG.md
77
78
  - DESIGN.md
78
79
  - LICENSE.txt
@@ -254,7 +255,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
254
255
  requirements:
255
256
  - - ">="
256
257
  - !ruby/object:Gem::Version
257
- version: 3.3.0
258
+ version: 3.4.0
258
259
  required_rubygems_version: !ruby/object:Gem::Requirement
259
260
  requirements:
260
261
  - - ">="