Creates the _new version of the field editor element alongside the working original (which remains untouched). The _new file starts from the original's hardened optimistic-update state machine and adds: - Svelte 5 generics (T) on current_value/draft_value instead of any - email / url / tel added to the field_type union with edit-mode branches - object_reload prop removed (was declared, never implemented — on_success is and remains the caller's cache-refresh hook) - to_input_value() / from_input_value() stubs at the two right call sites for datetime conversion (both directions, TODO #4 to implement) - coerce_select_value() stub for select type-mismatch fix (TODO #5) - Inline TODO mini how-to checked-list mirroring the project doc - Styling still uses Skeleton classes — tagged for Tailwind/Flowbite swap (TODO #6) and a11y gaps marked at their exact markup locations (TODO #7) Companion files: - documentation/PROJECT__AE_Obj_Field_Editor_New.md — full plan, migration order for all 8 call sites, naming convention note - documentation/TODO__Agents.md — new entry pointing to the project doc - documentation/README__Docs_Index.md — project doc added to Active Projects npx svelte-check: 0 errors, 1 expected benign warning (state_referenced_locally on the field_type initializer, documented in-file). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
Project: AE Obj Field Editor — _new Rewrite
Status: 🟡 Planning
Priority: Medium — quality-of-life primitive, no event deadline attached
Created: 2026-06-16
Last Updated: 2026-06-16
Related: BOOTSTRAP__AI_Agent_Quickstart.md, AE__Naming_Conventions.md
Background
src/lib/elements/element_ae_obj_field_editor.svelte is the generic inline
field editor: given object_type + object_id + field_name + current_value,
it renders a "click pencil → edit one field → Save" UI and PATCHes just that
field via api.update_ae_obj(). It's used in 8 call sites today (session_view,
presenter_view, location_view, person_view, device list, presentation list,
location list, leads manage tab).
This component already went through one rename cycle — it was originally built
as element_ae_obj_field_editor_v3.svelte (replacing an older CRUD v1/v2
pattern), then renamed to drop the _v3 suffix once it became canonical. That
migration (build new → migrate callers one at a time → delete old → drop
suffix) worked well and is the template for this project.
Review findings (2026-06-16) — see REFERENCE__Common_Agent_Mistakes.md
context and the chat log for full detail:
- Built entirely on Skeleton UI classes (
btn-icon,variant-soft-*,variant-filled-*,.input/.select/.textarea/.checkbox,badge). Conflicts with the stated Tailwind v4 + Flowbite/ShadCN direction. This is the highest-leverage place to do that swap — fix once inelements/, every call site benefits. object_reloadprop is dead. Declared, defaults totrue, commented "SWR pattern" — never read in the script. Every call site compensates by hand-rolling identicalon_success={() => events_func.load_ae_obj_id__*(...)}boilerplate. Either implement it for real or remove it and documenton_successas the actual refresh mechanism.- Datetime fields need manual format conversion at every call site, only
on the way in, never on the way out. Confirmed in
session_view.svelte: callers must pre-convert withto_datetime_local(...)before passingcurrent_valuebecause the component does no normalization between the stored datetime string and<input type="datetime-local">'s expected format. On save, the native datetime-local string goes straight to the PATCH body with no reverse conversion. Works today because the backend is forgiving, but the contract is leaky. - Select-binding type mismatch is a latent landmine.
Object.entries()always yields stringvals for<option value>. Today's usages (event_location_id,poc_person_id) are string IDs already, so it's invisible — but a true numeric/boolean enum field would break the optimistic-clear check (current_value === draft_value, strict equality), leaving the field stuck on the optimistic value. - Missing field types: no
email(no native keyboard/validation), nourl/tel. Original design intent explicitly wanted email support. current_value/draft_valuetypedany— no compile-time safety. Svelte 5 supports generics (<script generics="T">); this primitive should use them.- Minor a11y/polish gaps: invisible edit-trigger button stays
tab-focusable when
$ae_loc.edit_modeis off; icon-only Save/Cancel buttons lackaria-label; error message is tooltip-only (titleattr, not visible/announced); no Escape-to-cancel; no autofocus on entering edit mode. - Coarse-store cost (not a bug, just a known tradeoff): reads
$ae_loc.edit_modedirectly in the template. Fine in isolation, but this component is instantiated per-row in list tables, so any unrelated$ae_locwrite re-renders every instance on the page. Not chasing this now — noting it in case a list ever feels janky.
What's not broken and should be preserved as-is: the optimistic-display
state machine (has_optimistic / draft_value / clearing once current_value
catches up) is already hardened — it went through a real bug-fix round
(layout shift, bindable crash, optimistic display) and works correctly for
every field type in production use today.
Goals
- Near drop-in replacement. Same prop contract (names, types, defaults) wherever possible, so migrating a call site is an import swap plus maybe one or two new optional props — not a rewrite of the call site.
- Don't let "drop-in" block real fixes. Where the old contract is part
of the problem (e.g.
object_reloaddoing nothing, datetime conversion being the caller's job), change it — call sites get updated during migration anyway, so this is the moment to fix it properly rather than carry the leak forward. - Run both versions in parallel during migration. The old file stays in place and fully working until every call site has migrated and been verified. No "migrate and pray" — verify each call site, then move to the next.
- One canonical "_new" rewrite. Per project naming convention going
forward: when rewriting an existing thing, the in-progress replacement is
named
_new(not_v2/_v4/etc.) regardless of how many older versioned files may have existed historically for other components. There should only ever be one "new version of whatever we're working on" at a time. Once migration is complete, drop the_newsuffix and delete the old file — exactly as was done for the previous_v3→ (no suffix) rename.
Non-Goals
- Multi-field / batch editing in one widget — out of scope, this stays a single-field editor by design.
- JSON sub-field editing (
cfg_json.some_key) — not a supported field_type today, not adding it here either. - Changing the underlying
api.update_ae_obj()PATCH semantics.
New Component: element_ae_obj_field_editor_new.svelte
Prop contract changes from the original
| Prop | Change | Why |
|---|---|---|
object_reload |
Removed. | Never implemented; on_success is and remains the real refresh hook. Removing rather than silently leaving a dead, misleading prop. |
field_type |
Add 'email' | 'url' | 'tel'. |
Explicitly wanted in the original design, never added. |
current_value / draft_value |
Typed via generic (<script generics="T">) instead of any. |
Compile-time safety for callers; this is meant to be used consistently project-wide. |
| Datetime conversion | Owned by the component, both directions (stored format ↔ datetime-local/date input format). |
Removes the to_datetime_local(...) boilerplate currently required at every datetime call site, and fixes the missing reverse conversion on save. |
| Select value coercion | Coerce draft_value back to typeof current_value after a select change (or compare via String() in the optimistic-clear check). |
Prevents the stuck-optimistic-state landmine for any future numeric/boolean enum field. |
Everything else (object_type, object_id, field_name, allow_null, select_options, edit_label, display_block, display_absolute_edit, placeholder, class_li, textarea_rows, log_lvl, on_success, on_error, children) |
Unchanged. | Drop-in for every call site that doesn't use object_reload. |
Styling
Rebuilt on Tailwind v4 utility classes + Flowbite, matching whatever the
current non-Skeleton convention is elsewhere in the app (check a recently
touched component, e.g. the Pres Mgmt Config page, for the current baseline
look). No Skeleton classes (btn-icon, variant-*, .input/.select/etc.)
in the new file.
Polish fixed opportunistically while rewriting
aria-labelon icon-only Save/Cancel/edit-trigger buttons.tabindex="-1"on the edit-trigger button when invisible ($ae_loc.edit_modeoff), so it's not in the tab order while non-interactive.- Visible inline error text (not just a
titletooltip) whenpatch_status === 'error'. - Escape key cancels edit mode (mirrors existing Enter-to-save on text/number).
- Autofocus the input when entering edit mode.
Migration Plan
- Build
element_ae_obj_field_editor_new.svelteinsrc/lib/elements/, alongside the existing file. Keep the old file completely untouched and working during this phase. - Smoke-test in isolation — add it to
src/routes/testing/ae_obj_field_editor/+page.svelte(the existing test playground for the old component) alongside the old one, covering everyfield_typeincluding the newemail/url/tel. - Migrate call sites one at a time, in this order (simplest/lowest-risk
first, datetime-heavy ones last since they exercise the riskiest contract
change):
src/routes/core/person_view.sveltesrc/routes/events/[event_id]/(pres_mgmt)/device/device/ae_comp__event_device_obj_li.sveltesrc/routes/events/[event_id]/(pres_mgmt)/locations/ae_comp__event_location_obj_li.sveltesrc/routes/events/[event_id]/(pres_mgmt)/location/[event_location_id]/location_view.sveltesrc/routes/events/ae_comp__event_presentation_obj_li.sveltesrc/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__manage.sveltesrc/routes/events/[event_id]/(pres_mgmt)/presenter/[presenter_id]/presenter_view.sveltesrc/routes/events/[event_id]/(pres_mgmt)/session/[session_id]/session_view.svelte(has the datetime fields — migrate last, drop theto_datetime_local(...)wrapper at the call site once the component owns that conversion)
- Per call site: swap the import, remove any now-unnecessary
object_reloadusage (there is none today — confirmed dead) and any manual datetime pre-conversion, runnpx svelte-check, visually verify the field in the running app (view mode, edit mode, save, optimistic display, error path). - Once all 8 call sites + the test playground are migrated and verified:
delete
element_ae_obj_field_editor.svelte(move to~/tmp/agents_trash, neverrm), renameelement_ae_obj_field_editor_new.svelte→element_ae_obj_field_editor.svelte, update the now-stale import paths left by the rename, runnpx svelte-checkone final time. - Update docs:
AE__Naming_Conventions.mdif a "_new" rewrite convention note belongs there,BOOTSTRAP__AI_Agent_Quickstart.md/REFERENCE__Common_Agent_Mistakes.mdif anything generalizable came out of the datetime/select-coercion fixes, and mark this doc complete.
Both files coexist for the full duration of steps 1–4. Nothing is deleted until every consumer is migrated and verified.
Open Questions
- None blocking the start of step 1. Revisit field-by-field styling choices (e.g. exact Tailwind/Flowbite input classes) against whatever the most recently styled form in the app is using, since Skeleton phase-out is ongoing and conventions may still be settling.
Naming Convention Note (for future rewrites like this)
Going forward, an in-progress full rewrite of an existing component/module
is named _new regardless of how many historically-versioned files
(_v1/_v2/_v3/etc.) may exist elsewhere in the codebase for unrelated
components. There should be at most one "new version of the thing we're
currently rewriting" in flight at a time. Once migration completes, the
suffix is dropped and the old file is deleted — there is no permanent _new
file left behind, just like there's no permanent _v3 file left behind from
the previous round of this same component.