Skip to content

feat(editor): give the six 0.5/0.6 fields a form, so adopters can publish them - #106

Merged
hhkaos merged 1 commit into
mainfrom
feat/editor-0.5-fields
Nov 4, 2026
Merged

hhkaos merged 1 commit into
mainfrom
feat/editor-0.5-fields

Conversation

@hhkaos

@hhkaos hhkaos commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Closes #104. Part of #101 — this was the last unchecked app gap there besides publish.

The editor writes the documents a community actually publishes, so a field it has no form for is a field that does not exist for most adopters. Six had arrived since 0.4 and none could be authored here. The repo now implements OTE 0.6.0 in the editor (pin checked first: @opentechevents/schema was already at 0.6.0, no bump needed).

Event fields

  • speakers[] — name and affiliation are two inputs, deliberately and prominently. An agenda writes "Martí Cristobal, Bettatech"; with one field the company ends up inside name, where that person stops matching themselves anywhere else. Plus url, type, and a comma-separated sameAs.
  • recordings[] — a bare URL until a title (or a translation of one) earns the object form, exactly as image does. Its note states the flow it belongs to: added after the event, same id, new updatedAt.
  • contributing — an HTTP(S) URL or a mailto: role address, as a per-event override. Hidden by the meetup profile for the same reason license is: it is inherited from the feed, not per-event work.

Feed fields (Feed settings → ote.config.json)

  • contributing, the primary home: set once, every event inherits it.
  • covers — the completeness window. The form carries the hint that matters: this is what makes deleting old events honest, since without it absence means "not happening", and a community that prunes 2023 asserts those events never existed. Clearing both bounds removes the block rather than leaving an empty claim behind.
  • alternates[] — media type is a picker, since a client renders "RSS"/"iCalendar" from it in the reader's own language.

Multi-part pricing

A Shared admission (multi-part) block in Tickets writes partOf.offers, and each of the event's own offers gains a Scope select (admission / add-on). The shared block stays behind a "+" chip even inside an opened Tickets section, so a conference event never shows two ticket repeaters at once and invites filling in the wrong one; the meetup profile hides it entirely, since a meetup is one document with no set of parts to price. Both serialize through one cleanOfferRows helper, because the validator compares the parts' blocks by value (D032) and they must match byte for byte.

A silent data loss fixed on the way

The JSON-LD importer has produced speakers and recordings since #96, but fromEventJson never read them — so every import dropped both on the floor. Round-trip now holds for all six fields; a loaded event keeps what the form previously did not model.

Notes for review

  • renderChippableSection now dispatches on REPEATER_KEYS membership instead of an enumerated organizers/image pair, so a newly registered repeater works at all four call sites.
  • alternates is deliberately in REPEATER_SPECS only and not in FIELD_REGISTRY — it is a feed field no event carries, and registering it would offer it as an event field and list it in the custom-profile checklist.
  • PATH_TO_FIELD gains partOf.offers before partOf; the list is first-match-wins, so the reverse order would report its errors against a field in a different section.
  • packages/build-feed's types needed three additions the 0.5 work left out: OteOffer.scope, OtePartOf.offers, OteRecordingEntry.translations.
  • apps/editor/CLAUDE.md gains a section on the two-table repeater registration and the serialization sites that are easy to do three-quarters of — a field missing only from fromEventJson is the quiet one, which is exactly how the importer bug survived.

Verification

1428 tests passing workspace-wide, 369 in the editor (+22 new); typecheck and build clean. Driven in a real browser against the running app: all six filled in, badge ✓ Ready, and the review dialog emits the intended JSON — scope: "add-on" alongside partOf.offers, sameAs split to an array, the recording as {url, title}. The Feed settings save payload carries contributing/covers/alternates. Translation cards grow "Recording 1 title" and "Shared tier 1 name" slots. No console errors. Downstream checked rather than assumed: the exporters already read scope/add-on, so what the editor writes now exports correctly.

Not included

The "conference mode" sketched at the end of #104 — writing the shared block once and then only asking for per-talk fields. That is a submission flow, not a field, and it is the same mechanism as #51; both are tracked separately so they get one design pass instead of two competing repeaters.

🤖 Generated with Claude Code

…lish them

The editor writes the documents a community actually publishes, so a field
it has no form for is a field that does not exist for most adopters. Six
had arrived since 0.4 (#104) and none of them could be written or edited
here.

Event-level:

- speakers[]: name and affiliation are two inputs, deliberately and
  prominently. An agenda writes "Martí Cristobal, Bettatech"; with one
  field the company ends up inside name, where that person stops matching
  themselves anywhere else. Plus url, type and a comma-separated sameAs.
- recordings[]: a bare URL until a title (or a translation of one) earns
  the object form, exactly as image does. Added after the event happened,
  so its note says what that flow is: same id, new updatedAt.
- contributing: an HTTP(S) URL or a mailto: role address, as a per-event
  override. Hidden by the meetup profile for the same reason license is —
  it is inherited from the feed, not per-event work.

Feed-level, in Feed settings (ote.config.json):

- contributing, the primary home: set once, every event inherits it.
- covers: the window the feed claims completeness for. The hint the form
  carries is the one that matters — this is what makes deleting old events
  honest, since without it absence means "not happening" and a community
  that prunes 2023 asserts those events never existed. Clearing both
  bounds removes the block rather than leaving an empty claim behind.
- alternates[]: media type is a picker, since a client renders
  "RSS"/"iCalendar" from it in the reader's own language.

Multi-part pricing: a "Shared admission (multi-part)" block in Tickets
writes partOf.offers, and each of the event's own offers gains a scope
select (admission / add-on). The shared block stays behind a "+" chip even
inside an opened Tickets section, so a conference event never shows two
ticket repeaters at once and invites filling in the wrong one; the meetup
profile hides it entirely, since a meetup is one document with no set of
parts to price. Both serialize through one cleanOfferRows helper, because
the validator compares the parts' blocks by value and they must match.

Fixes a silent data loss on the way: the JSON-LD importer already produced
speakers and recordings, but fromEventJson never read them, so every
import dropped both. Round-trip now holds for all six.

renderChippableSection now dispatches on REPEATER_KEYS membership instead
of an enumerated organizers/image pair, so a newly registered repeater
works at all four call sites; alternates is deliberately in REPEATER_SPECS
only and not in FIELD_REGISTRY, being a feed field no event carries.
PATH_TO_FIELD gains partOf.offers BEFORE partOf, or its errors would be
reported against a field in a different section.

Verified in a browser against the running app: all six filled, badge
"Ready", and the review dialog emits the intended JSON (scope add-on
alongside partOf.offers, sameAs split to an array); the Feed settings save
payload carries contributing/covers/alternates. Spec pin checked first and
already at 0.6.0.

Not included: the "conference mode" sketched at the end of #104 — writing
the shared block once and then only asking for per-talk fields is a
submission flow, not a field, and keeps its own issue.

Co-Authored-By: Claude Opus 5 <[email protected]>
@hhkaos
hhkaos merged commit 191de32 into main Nov 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

editor: no form for speakers, recordings, contributing, alternates, covers or multi-part pricing

1 participant