feat(package): notes field, activity log, and document list field - #161
Open
horner wants to merge 30 commits into
Open
feat(package): notes field, activity log, and document list field#161horner wants to merge 30 commits into
horner wants to merge 30 commits into
Conversation
Add a reusable `@esheet/document-list-field` package backed by DataVis, including document normalization, grid rendering, toolbar actions, detail rows, and provider-based host integration. Add generic `FieldProviderStack` support to the builder, renderer, and Blaze renderer so field add-ons can receive host behavior without changing form response data. Register the document-list field in the demo and demonstrate Compose, Upload, detail expansion, custom detail rows, and YAML schema loading. Add focused unit tests and workspace build configuration for the new package.
- Add package-owned Compose and Upload modals - Support editable document metadata and note content - Pass transient text and file content through the repository contract - Add text/image detail previews with metadata fallback - Move demo persistence to in-memory document/content maps - Remove host-owned upload and compose handlers - Add workflow tests, styles, and exports
New built-in fieldType 'notes' — an array of rich (markdown) note entries, each optionally carrying attachments: - NotesFieldDefinition (allowAttachments/accept/maxFileSize/ maxAttachments/maxNotes/sortOrder/entryLabel) in the FieldDefinition union, strict zod schema, and normalizeFormDefinition property list. - noteEntrySchema / NoteEntry: GUID id (stable across edits, the unit of merge), createdAt/updatedAt, optional author, raw markdown body, optional attachments reusing AttachmentAnswer (now with a zod schema). - FieldResponse.notes, AnswerValue gains NoteEntry[], answerType 'notes' flows through extractResponseValue and hydrateResponse. - Registry meta (label Notes, category rich, defaults entryLabel=Note, sortOrder=newest); required means >=1 entry in validation. 420 core tests pass (12 new).
- mergeNotes(a, b): union by GUID id; same-id conflicts resolve last-writer-wins on updatedAt ?? createdAt; output sorted by createdAt. Ships in core so every host resolves conflicts the same way (CRDT hosts call it when both sides changed a notes response). - collectAttachments / mapAttachments: traverse every attachment in a response — fileData and notes[].attachments — so externalize/ rehydrate pipelines don't hardcode field shapes. mapAttachments returns the same reference when there is nothing to rewrite. 433 core tests pass (13 new).
pnpm v10 defaults linkWorkspacePackages to false, so @esheet/* deps resolved to the last published registry tarballs — any PR that adds an export to core or fields could not typecheck or bundle in dependent packages. Also externalize @esheet/fields in the builder bundle for the same shared-registry reason as the renderer (PR #152).
…ation Journal-style notes field (fieldType 'notes'): - Read-only card list sorted per sortOrder (newest default): author + created/edited timestamps in the header, body rendered through the shared markdown pipeline. That pipeline is DisplayField's renderer extracted verbatim to lib/markdown.tsx (DisplayField now imports it; no new renderer dependency). - Composer: Add button -> textarea with Write/Preview tabs; Save stamps GUID/createdAt (author from host identity when present), Edit reuses the composer and stamps updatedAt, delete requires inline confirmation. maxNotes hides Add; canModify hook gates per-entry edit/delete (host policy — server-side enforcement is the host's). - Attachments when allowAttachments: picker enforcing accept / maxFileSize / maxAttachments via lib/file-utils.ts (formatFileSize + accept matching extracted from FileField, which now shares them). - NoteCardList is a separate presentational list so the upcoming read-only activity log can reuse it without copying. - Builder mode edits question/entryLabel/sortOrder/maxNotes/attachment props in-component (same pattern as FileField); registered in both renderer and builder defaults. 13 new DOM tests; all 6 packages green (593 tests).
EsheetRenderer gains identity?: { name: string }, kept in new form-store
state (setIdentity). NotesField stamps new entries' author from it; no
identity -> notes save unstamped. No breaking change: the prop and the
store field are both optional.
…xport - Builder palette needs no change (registry-driven); NotesField's builder mode already edits entryLabel/allowAttachments/accept/limits/ sortOrder in-component like FileField. - Logic: 'notes' answers support empty/notEmpty conditions (LogicEditor operator list + core condition evaluation: count for numeric access, entry ids for value access). - FHIR export: notes map to a repeating text item; each entry flattens human-readably (author/timestamp header, raw markdown, attachment titles). Structured FHIR round-trip and SurveyJS/MCP mappings are deferred follow-ups to note in the PR description. - Submission payload already carries NoteEntry[] via core hydrateResponse (covered in the phase-1 tests).
New built-in fieldType 'activity': a read-only page any eSheet can include that renders a log of response changes over time. - Core: ActivityEntry (GUID id, at, author, fieldId, question, display from/to) under the reserved _activity response key. The form store emits an entry on every setResponse when the form contains an activity field; consecutive changes to the same field within 2s collapse into the last entry (original 'from' preserved) so keystrokes don't flood the log. Author comes from the identity state; display values via extractResponseValue + formatActivityValue. - mergeActivity reuses the notes merge, now generalized as mergeById (union by GUID, LWW on stamp) — no copied merge logic. - Fields: ActivityField renders the log newest-first through the same NoteCardList as NotesField, with zero mutation affordances (asserted in DOM tests); registered in renderer and builder defaults. - Validation skips activity fields (non-input); answerType 'display' keeps it out of submission payloads. Core 440 and fields 33 tests pass (11 new); all packages green.
Field-type pages with YAML examples, answer formats, merge semantics, authorship/edit-policy guidance, and the attachment traversal helpers; sidebar + overview updated. Full build and test sweep green across all packages (607 tests).
…mposer NotesField's composer is now a registration point: hosts call registerNotesComposer() from @esheet/fields to swap the default textarea (Write/Preview tabs) for a rich editor. Markdown stays the storage format either way, so composed notes render identically in the read-only card list and the textarea default remains for hosts that don't want the weight (~1.4 MB JS). @esheet/field-kerebron ships KerebronNotesComposer: the same CoreEditor + AdvancedEditorKit markdown load/save loop as RichTextEditorField (debounced changed-event flush, final flush on unmount, fresh mount node for destroy()). @esheet/fields is externalized there for the same shared-registry reason as renderer/builder. All packages green (627 tests).
…undle @kerebron/* pin prosemirror-model 1.25.9 exactly while other prosemirror libs float to 1.25.11, so the bundle carried both — at runtime typing threw 'Can not convert to a Fragment (looks like multiple versions of prosemirror-model were loaded)' because DOM changes parsed with one model against a schema built with the other. Workspace override pins 1.25.9; the dist now contains a single copy.
…, serialize loads Two runtime fixes found wiring KerebronNotesComposer into a host: - loadDocumentText always runs, even for an empty note: without an initial document the editor logs 'NO NODE at 1' and swallows input. - Initial loads are serialized through a module-level queue (the same pattern as echart-sim's DocumentComposeEditor): the markdown converter's tree-sitter WASM init is not concurrency-safe, so parallel mounts (React StrictMode, multiple notes fields) raced into 'cannot construct a Parser before calling init()'. A disposed guard skips loads whose editor was destroyed while queued. Browser-verified in eCase: compose, save, and edit round-trip through markdown with the WASM served same-host.
The field wrapper was static, so the absolutely positioned dots flew to the page corner. Keying by name also collapsed two windows of one person into one dot, and the dot size came from a utility a host that only loads the fields stylesheet does not have.
Deploying esheet with
|
| Latest commit: |
f3a8d3e
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://2497d978.esheet.pages.dev |
| Branch Preview URL: | https://feat-notes-field.esheet.pages.dev |
Touch mode forced min-height/font-size on every button under the renderer root, which oversized the DataVis chrome; exempt [data-slot] buttons and size the document-list title actions directly, since the overlay never inherits body.condensed.
eSheet carries whole files inline as base64 data URLs. That is fine for a demo and ruinous for a host that persists the response — a CRDT-backed host pays the +33% again on every edit, forever. Adds an optional AttachmentManager (store/load/remove) in @esheet/core, delivered through the existing fieldProviders stack. FileField stores on pick and removes on delete; the NotesField composer stores on save, so a cancelled composer leaves nothing behind. With no provider both fields behave exactly as before, inline.
A host that stores document bytes outside the response needs to address them from the row alone. eSheet never computes sha256/size; it carries them through normalization untouched.
# Conflicts: # packages/field-kerebron/package.json
Collaborator
IMG_8895.MOV |
…e the noun The compose and upload forms were @mieweb/ui modals: they dimmed the page and shrank the form to a narrow dialog, so writing a note felt like working through a magnifying glass. They are now a panel that covers the document list it belongs to — full width, full height, header with a close button, scrolling body, Escape to dismiss, first field focused on open. Two things the forms hard-coded are now the field's to say: - `noun` names one row, so the same field reads as "Compose note", "Compose letter", or the default "Compose document" — in the toolbar labels, the panel title, and the save button. - The compose form asks for the columns the list shows. A list without a `subject` column no longer asks for a subject, and its validation message names only the fields it rendered. Title is always asked for: it is the row's name. DocumentListComposeModal and DocumentListUploadModal are renamed to …ComposePanel and …UploadPanel, and are rendered only while open.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Summary
Overview
Adds a notes/annotation feature set across the stack, a new
@esheet/document-list-fieldpackage, an append-only activity page type, and a pluggable rich-text composer backed by Kerebron.Highlights
Core (
types.ts,registry.ts, stores/form-store.ts)notesfield definition +NoteEntryresponse shape, registered in the field registry.mergeNotesand attachment-traversal helpers in the form store, with new specs.Fields (
NotesField.tsx)NotesField: entry list, composer, attachments, author stamping.ActivityFieldfor the append-only response change log.AttachmentManagerseam so hosts can own attachment bytes (AttachmentManagerProvider.tsx).markdown,file-utils, andnotes-composerhelpers;DisplayField/FileFieldrefactored onto them.Kerebron (
KerebronNotesComposer.tsx)prosemirror-modelto a single copy, and a patch for@kerebron/extension-menutheming.Document List Field (new package
document-list-field)Renderer / Builder / Adapters
identityprop for author stamping; presence dots now land on the field, one per peer.Scope
94 files, ~8.9k insertions / ~509 deletions (includes lockfile churn).
Verification
Recommend running
pnpm lint && pnpm test && pnpm typecheck && pnpm buildpluspnpm format:checkbefore merge.