@lotics/ui 43.1.0 → 43.3.0
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.
- package/MIGRATION.md +44 -0
- package/docs/catalog.md +6 -3
- package/docs/data_entry.md +53 -21
- package/docs/templates.md +130 -27
- package/examples/tpl_record.tsx +349 -272
- package/package.json +1 -1
- package/src/inline_edit.tsx +34 -1
- package/src/inline_text_input.tsx +19 -4
- package/src/summary.tsx +7 -1
- package/src/timeline.tsx +16 -5
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,37 @@ Breaking changes, newest first — normally per major, plus the rare minor that
|
|
|
4
4
|
anyway (recorded under its exact version). The current contract lives in `AGENTS.md` + `docs/`;
|
|
5
5
|
this file exists only to move an app from one release to the next.
|
|
6
6
|
|
|
7
|
+
## 43.3.0 — a `bare` inline field sits on the column, and Enter finishes a one-line one
|
|
8
|
+
|
|
9
|
+
Two fixes to `Inline*`, both of them behaviour a caller cannot see in a prop name.
|
|
10
|
+
|
|
11
|
+
**A `bare` field now aligns its TEXT, not its box.** The field surface carries 8px
|
|
12
|
+
of horizontal padding, which is invisible at rest on a variant that draws no
|
|
13
|
+
border — so every bare value rendered 8px right of whatever named it. In a
|
|
14
|
+
`DataGrid` that meant each column's values sat off its own header. The inset is
|
|
15
|
+
now bled back out with a negative LEFT margin: glyphs land on the container's
|
|
16
|
+
edge, the hover/focus border overhangs into the gutter, and hover moves nothing.
|
|
17
|
+
|
|
18
|
+
Left only, deliberately. A matching right bleed aligns nothing on left-aligned
|
|
19
|
+
text and pushes the frame past the edge its neighbours stop at, which shows the
|
|
20
|
+
moment the border paints. **If you compensated with your own negative margin,
|
|
21
|
+
remove it** — you will now be 8px out the other way. A field needs ~8px of slack
|
|
22
|
+
on its left to hover into; in a zero-gap column, adjacent borders will touch. A
|
|
23
|
+
DISABLED `framed` field is unaffected: it drops its border and fill so it cannot
|
|
24
|
+
promise a press, but it stays in line with the enabled fields beside it.
|
|
25
|
+
|
|
26
|
+
**`numberOfLines` now decides what ENTER does — wrapping no longer does.** The two
|
|
27
|
+
were one flag, so `autoGrow` — a prop about fitting the value — silently turned
|
|
28
|
+
Enter from "save" into "insert a newline". A one-line summary field that grew when
|
|
29
|
+
it ran long became a field you could only leave by clicking away, and nothing at
|
|
30
|
+
the call site said so.
|
|
31
|
+
|
|
32
|
+
A field left at one line (or `numberOfLines={1}`) holds a single-line value that
|
|
33
|
+
may WRAP, and Enter commits it. Declaring 2+ lines declares PROSE: Enter is a
|
|
34
|
+
paragraph break and blur saves, exactly as before. **If you wrote
|
|
35
|
+
`numberOfLines={1} autoGrow` and relied on Enter inserting a newline, raise the
|
|
36
|
+
line budget** — that is now what says so.
|
|
37
|
+
|
|
7
38
|
## 43.0.0 — a time is PICKED, and there is nothing to type
|
|
8
39
|
|
|
9
40
|
`TimePicker` is no longer a segmented text field. It is the value on a pressable
|
|
@@ -78,6 +109,19 @@ its reasoning are in `docs/composition.md` § The pointer cursor. If a component
|
|
|
78
109
|
needs the pointer, set `CURSOR_ACTION` in its style — and if it is built on the bare
|
|
79
110
|
react-native `Pressable`, note that the pointer is what you get by DEFAULT, from
|
|
80
111
|
react-native-web, unless you set `CURSOR_DEFAULT`.
|
|
112
|
+
## 42.5.0 — an expanded `Timeline` row shows its whole label
|
|
113
|
+
|
|
114
|
+
The label was clamped to two lines whether the row was open or shut. Expanding a
|
|
115
|
+
row is a request to see more of what is there, so a label that stays truncated
|
|
116
|
+
through it leaves the caller exactly one way to make its own label readable:
|
|
117
|
+
repeat it inside `details`. Then the reader meets the same sentence twice — once
|
|
118
|
+
cut off, once whole — which reads as a rendering bug rather than as disclosure.
|
|
119
|
+
|
|
120
|
+
It now clamps while collapsed only. **If you duplicated a label into `details`
|
|
121
|
+
to work around this, delete the copy**; the row will show it in full on its own.
|
|
122
|
+
Nothing else changes: a collapsed row is identical, and a row whose label already
|
|
123
|
+
fitted in two lines is identical open or shut.
|
|
124
|
+
|
|
81
125
|
## 42.4.0 — `Picker` is named by its `FormField`, and its way back to empty has a name
|
|
82
126
|
|
|
83
127
|
Two fixes to the same control, both about a thing on screen that the control did
|
package/docs/catalog.md
CHANGED
|
@@ -1487,9 +1487,12 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1487
1487
|
provider-wired — pass `labels` (`DEFAULT_GANTT_LABELS` is English).
|
|
1488
1488
|
- **`timeline`** — `Timeline`: a heterogeneous event LOG — per-row icon + expandable
|
|
1489
1489
|
details; models the past, NOT progress. Three things it decides for you, because a row
|
|
1490
|
-
cannot be trusted to a caller's data: the **label clamps to two lines
|
|
1491
|
-
description
|
|
1492
|
-
user-authored note set the section's height
|
|
1490
|
+
cannot be trusted to a caller's data: the **label clamps to two lines WHILE COLLAPSED**
|
|
1491
|
+
(the description always) — a row is a scan target, and an unclamped one lets a single
|
|
1492
|
+
user-authored note set the section's height. Opening the row un-clamps it, so **never
|
|
1493
|
+
repeat the label inside `details`**: the fact belongs to one element, and a copy means
|
|
1494
|
+
the reader meets the same sentence twice, cut off and then whole. `details` carries
|
|
1495
|
+
what the label does not — the attachments, the machine's reading, the provenance;
|
|
1493
1496
|
**`placeholder`** marks a label the SURFACE supplied rather than one the record
|
|
1494
1497
|
holds, drawing it in the unfilled-field ink — reach for it on any feed that fills
|
|
1495
1498
|
from more than one direction, where an entry routinely arrives with no words in it;
|
package/docs/data_entry.md
CHANGED
|
@@ -84,6 +84,14 @@ layout), click OR keyboard focus opens it for editing **with nothing moving** (z
|
|
|
84
84
|
whole point), and it commits on blur (Enter saves, Escape reverts) or via `controls="buttons"`
|
|
85
85
|
(✓ / ✕).
|
|
86
86
|
|
|
87
|
+
**`numberOfLines` declares what ENTER means — wrapping does not.** A field left at one line holds a
|
|
88
|
+
single-line value that may WRAP when it runs long (a title, a summary), and Enter finishes it.
|
|
89
|
+
Declaring 2+ lines declares PROSE: Enter is a paragraph break and blur is what saves. The two used
|
|
90
|
+
to be one flag, so `autoGrow` — a prop about fitting the value — silently turned Enter into a
|
|
91
|
+
newline, and a one-line summary field that grew became a field you could only leave by clicking
|
|
92
|
+
away. Nothing at the call site said so, which is what made it worth separating rather than
|
|
93
|
+
documenting.
|
|
94
|
+
|
|
87
95
|
**How "nothing moves" is achieved differs by editor, and the difference is a rule.** A field whose
|
|
88
96
|
resting value is the RAW string (`InlineTextInput`) is ONE `<input>` in both states — it never
|
|
89
97
|
swaps elements, and `editing` is a style state driven by focus. Two elements cannot be made to
|
|
@@ -277,6 +285,17 @@ already promises and, repeated down a column, draws the grid a second time. Both
|
|
|
277
285
|
identically, so this changes what a field looks like RESTING, never what it does. A hand-rolled
|
|
278
286
|
pressable cell should match `bare`: nothing at rest, the border on hover — never a background wash.
|
|
279
287
|
|
|
288
|
+
**A `bare` field aligns its TEXT, not its box.** Because it looks like text at rest, it sits on the
|
|
289
|
+
column like text: the frame's 8px inset is pulled back out with a negative LEFT margin, so the glyphs
|
|
290
|
+
land on the container's edge. Without that, every bare value rendered 8px right of the header naming
|
|
291
|
+
it. The bleed is one-sided on purpose — a matching right bleed aligns nothing on left-aligned text and
|
|
292
|
+
only pushes the frame past the edge its neighbours stop at, which shows the moment the border paints.
|
|
293
|
+
Two consequences for callers: a `bare` field needs ~8px of slack on its LEFT to hover into (in a
|
|
294
|
+
zero-gap column the borders of adjacent cells will touch), and it is the wrong variant for a surface
|
|
295
|
+
where it must stay strictly inside its box — that is `framed`. A DISABLED `framed` field keeps the
|
|
296
|
+
box: it drops the border and fill so it cannot promise a press, but it stays in line with the enabled
|
|
297
|
+
fields beside it.
|
|
298
|
+
|
|
280
299
|
A READ-ONLY value in the same column — a computed total, a system ID, a synced/locked field — is
|
|
281
300
|
**`InlineStatic`**: it copies the editor box metrics exactly (height, padding, 1px transparent
|
|
282
301
|
border) but stays FLAT and non-interactive, so editable (chip) vs read-only (flat) is legible at a
|
|
@@ -331,33 +350,46 @@ row that wants it. See the "General" section of `tpl_record`, which also reads t
|
|
|
331
350
|
full record surface. Not every field is a same-height swap — a tag field, a status, or an
|
|
332
351
|
attachment grid edit in place too (below).
|
|
333
352
|
|
|
334
|
-
##
|
|
353
|
+
## A value rendered TWICE is the bug — not the resident editor
|
|
354
|
+
|
|
355
|
+
The section above is the default and holds everywhere. There is one thing that appears to
|
|
356
|
+
contradict it, and the contradiction is worth naming because the wrong resolution is the
|
|
357
|
+
tempting one: **a value that already appears elsewhere on the same surface.**
|
|
335
358
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
359
|
+
A feed used to be the example. Its row LABEL was the gist, the body opened underneath, and a
|
|
360
|
+
resident editor rendered the same sentence twice — stacked and identical, which reads as a
|
|
361
|
+
mistake at any weight, so quieting the frame does not help. The fix looked like demoting the
|
|
362
|
+
editor to a mode behind an **Edit** verb, and that is a workaround: it keeps the duplication and
|
|
363
|
+
buys silence by hiding one copy behind a click. What it costs shows up immediately after —
|
|
364
|
+
the verb has to live somewhere (a button nested in the row's own button, or a footer a scroll
|
|
365
|
+
away from the sentence it edits), and every repair from there is a repair to the arrangement,
|
|
366
|
+
not to the surface.
|
|
340
367
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
mode,
|
|
368
|
+
**Render it once, and the question dissolves.** Text inside a press target cannot be edited where
|
|
369
|
+
it sits, so a value a person WROTE does not belong in a row's label: the label is for derived,
|
|
370
|
+
read-only text ("Stage changed to Won"). Move the prose out and it is an ordinary resident field
|
|
371
|
+
with no mode, no verb, and one copy — `tpl_record`'s activity feed is the worked example, and the
|
|
372
|
+
byline above it carries the derived facts that used to fight it for the row.
|
|
345
373
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
routinely — a recording lands, the row says it has not been written up, and the empty field is
|
|
349
|
-
the invitation to write it. Withhold it there and the feed can RECEIVE an entry it gives the
|
|
350
|
-
reader no way to finish. **An empty value shows its field; a filled one waits to be asked.**
|
|
374
|
+
So: if a resident editor would duplicate a value, **delete the duplicate**. Make the editor a mode
|
|
375
|
+
only when the second appearance is genuinely load-bearing and cannot move.
|
|
351
376
|
|
|
352
377
|
Two things follow that are easy to get wrong in the other direction:
|
|
353
378
|
|
|
354
|
-
- **
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
- **
|
|
360
|
-
|
|
379
|
+
- **Only the prose a PERSON wrote is editable.** Derived values (a header's participants),
|
|
380
|
+
verbatim ones (a transcript), artifacts (media, files) and a model's output (a summary — you
|
|
381
|
+
re-run it, you do not hand-edit it) are all read-only. If a surface does end up with an edit
|
|
382
|
+
verb, name it for the words rather than the row: "Edit", not "Edit entry", which claims the
|
|
383
|
+
whole entry is yours to rewrite.
|
|
384
|
+
- **An EMPTY value still shows its field, and shows it FRAMED.** A surface fed by automations
|
|
385
|
+
receives entries with no words in them routinely — a recording lands and nobody has written it
|
|
386
|
+
up — so withholding the field lets a feed RECEIVE an entry it gives the reader no way to
|
|
387
|
+
finish. Empty is also the state where `bare` fails: grey placeholder text on an invisible box
|
|
388
|
+
does not read as somewhere you can type. Follow the value — `variant={value ? "bare" : "framed"}`
|
|
389
|
+
— so a written-up entry reads as prose and an empty one is recognisable as an input from across
|
|
390
|
+
the page. A button that reveals a field is the wrong trade: `InlineTextInput` has no
|
|
391
|
+
`autoFocus` (permanently mounted, every field on a record would fight for focus on load), so
|
|
392
|
+
the revealed field cannot take the caret and the reader pays a second click for less clarity.
|
|
361
393
|
|
|
362
394
|
## Fieldset form — fields lay out on a RESPONSIVE two-column grid
|
|
363
395
|
|
package/docs/templates.md
CHANGED
|
@@ -461,12 +461,21 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
461
461
|
between us to get it there. It is NOT an audit trail and NOT the handoff — see the next bullet
|
|
462
462
|
for the line, which is what keeps this section from becoming the change log Progress refuses.
|
|
463
463
|
|
|
464
|
-
**The
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
464
|
+
**The GIST is what the feed is scanned by** — what came of the exchange, in the reader's own
|
|
465
|
+
words. Building it the other way round is the natural mistake and it makes the feed unreadable:
|
|
466
|
+
every entry renders as a pair of enum values ("Outbound Meeting"), the substance sits behind a
|
|
467
|
+
chevron, and a column of near-identical labels defeats the scan the feed exists for. Measured on
|
|
468
|
+
a real one, that shape spent 504px of chrome on 70px of content.
|
|
469
|
+
|
|
470
|
+
**An entry is a COMMENT, not a `Timeline` row.** `Timeline` renders its label inside the row's
|
|
471
|
+
press target, which is right for derived read-only text ("Stage changed to Won") and wrong for
|
|
472
|
+
the one thing on an entry a person WROTE — text inside a button cannot be edited where it sits.
|
|
473
|
+
Four repairs came out of working around that (the verbs moved off the row, the label learned to
|
|
474
|
+
un-clamp, the body's second copy of the gist came out, and Edit ended up a scroll from the
|
|
475
|
+
sentence it edits) and none were independent. So the gist is a RESIDENT field and the derived
|
|
476
|
+
facts sit above it on a muted `xs` **byline** — which way it went, over what, when. Order does
|
|
477
|
+
not decide the scan; weight does. The byline is small and grey, the prose is `sm` on the page's
|
|
478
|
+
own ground, and the eye lands on the substance even though it reads second.
|
|
470
479
|
|
|
471
480
|
**ONE anatomy, a body that VARIES.** An entry can be a call, a recorded demo, an email, a
|
|
472
481
|
message or a note typed between meetings — and those differ in what they CARRY, not in what
|
|
@@ -481,25 +490,96 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
481
490
|
|
|
482
491
|
| Block | What it is | How it renders |
|
|
483
492
|
|---|---|---|
|
|
484
|
-
| Participants | ONLY what cannot be derived — a third party who joined, an email's From/To. The record is already the counterparty
|
|
493
|
+
| Participants | ONLY what cannot be derived — a third party who joined, an email's From/To. The record is already the counterparty, so naming the obvious attendees is padding | muted line |
|
|
485
494
|
| Subject | the one thing an email has that nothing else does | `sm` medium |
|
|
486
495
|
| Media | the exchange itself, recorded | `MediaPlayer` — **give it a box**, it fills its parent and collapses to nothing without one |
|
|
487
496
|
| Transcript | VERBATIM and long; the source a summary was made from | a muted underlined `TextLink` in your own `Pressable`, revealing PLAIN text — see the disclosure exception in composition.md §"Commit & feedback surfaces" |
|
|
488
|
-
| Prose | what
|
|
497
|
+
| Prose | what the COUNTERPARTY sent — an email body. Not a second field of ours: an entry offering both a gist and a "note" gives one event two free-text boxes and nothing to tell a writer which to use, and the gist auto-grows, so it already carries whatever length someone wants | `Markdown variant="embedded"` on the page's own ground |
|
|
489
498
|
| AI prose | what a MODEL wrote | ONE short label INSIDE the `Inset` ("AI summary") — floating above it, a 12px muted fragment over a tinted box reads as an orphan. Name the evidence only where it is NOT on screen: over a player the reader can see, "from the recording" is four words restating a video. Let the label replace the block's own `## Heading` rather than sit above it |
|
|
490
|
-
| Files | what came with it |
|
|
499
|
+
| Files | what came with it — EVIDENCE, so it is writable | one `InlineFiles` (`onRemove`, no `onAdd`), mixed pdf/image, previewing in place |
|
|
491
500
|
| Source | where it happened, if openable | `Link` |
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
501
|
+
|
|
502
|
+
**An entry's files are WRITABLE, on both paths.** A touchpoint is very often about a document —
|
|
503
|
+
you sent the quote, they sent the PO back — so a feed that displays `files` but offers no way to
|
|
504
|
+
file one is a read-only surface over a writable model, which reads as a missing feature every
|
|
505
|
+
time. Two places, because there are two moments: the section's `Composer` stages them on the
|
|
506
|
+
entry being logged (`actionsButton` + the `files` slot + `sendDisabled`, the three kit slots the
|
|
507
|
+
comment thread already used), and the entry's own menu attaches to one that already exists.
|
|
508
|
+
|
|
509
|
+
**`sendDisabled` is not optional once a composer can attach.** The default blocks send on empty
|
|
510
|
+
text, which is right for a message and wrong the moment an attachment is the whole point — "here
|
|
511
|
+
is the signed PO" needs no sentence. Pass `false` while files are staged, `undefined` otherwise
|
|
512
|
+
so the default returns.
|
|
513
|
+
|
|
514
|
+
**Attaching lives on the MENU, not on the file list.** The list's own CTA cannot work on an entry
|
|
515
|
+
with nothing attached — there is no list to hang it off — so a menu verb that works in every
|
|
516
|
+
state is the single path, and the list takes `onRemove` alone. Removing CONFIRMS: that file is
|
|
517
|
+
already in the customer's history, unlike a staged attachment on the composer, which is not yet
|
|
518
|
+
anything.
|
|
519
|
+
|
|
520
|
+
**An entry is its own `FileDropTarget`**, nested inside the record-level one. A file dropped on a
|
|
521
|
+
conversation belongs to that conversation; routing it to the record's Documents intake instead
|
|
522
|
+
leaves the reader to relate the two by hand. Nesting is sound rather than lucky — the component
|
|
523
|
+
stops propagation on every drag/drop event, so the innermost region wins, the outer one neither
|
|
524
|
+
fires nor lights, and the file is added once. Pass a plain child so the target paints its own
|
|
525
|
+
drag affordance: with two live regions the drag has to say WHICH one it is about to land in.
|
|
526
|
+
Both paths — the menu verb and the drop — call ONE append function, because two implementations
|
|
527
|
+
of "add to this list" is how one of them quietly becomes a replace. A section earns its own
|
|
528
|
+
target when the drop MEANS something different there; "the same thing, closer" does not.
|
|
529
|
+
|
|
530
|
+
**A MESSAGE's attachments are tiles — staged or filed.** The composer's `files` slot takes
|
|
531
|
+
`<FileGrid files={…} itemSize={THUMBNAIL_SIZE} onDisplayRemove={…}>` — the exact component and
|
|
532
|
+
size the product's own chat composer passes — because a staged file is not yet a document in a
|
|
533
|
+
list: it is a thing you are about to send, checked by LOOKING at it, and the control that
|
|
534
|
+
unstages it belongs on the tile. Getting this backwards is easy and invisible: a row of staged
|
|
535
|
+
attachments looks tidy and quietly makes the composer feel like a file manager.
|
|
536
|
+
|
|
537
|
+
Filing it does not turn it into a row — it turns it into a `FilesEditor`. Tiles either way, the
|
|
538
|
+
same as `chat_user_message` renders a posted message's attachments and the same as this record's
|
|
539
|
+
Files section, because evidence is recognised by LOOKING (a scanned PO, a photo of a seal). The
|
|
540
|
+
rows belong to a different object: `InlineFiles` is a labelled FIELD in a `DetailRow` ("Signed
|
|
541
|
+
contract", "Supplier original"), where the document set has a name, a cardinality and a
|
|
542
|
+
`blockedReason`. Reasoning from that field to a message's attachments is the mistake to avoid —
|
|
543
|
+
it is not "the filed version of the same thing".
|
|
544
|
+
|
|
545
|
+
**A FILED attachment must be hard to lose; a STAGED one is not yet anything.** That is the whole
|
|
546
|
+
difference between the two components. A bare `FileGrid` draws a ✕ on every tile, one click from
|
|
547
|
+
gone — right in a composer, wrong on a record's history. `FilesEditor`'s default view never draws
|
|
548
|
+
one at all: removal means opening the file and doing it from the gallery, where you are looking
|
|
549
|
+
at the thing you are about to delete and it still asks, naming the file. Reach for the editor and
|
|
550
|
+
three things you would otherwise hand-roll come with it — the confirmation, the full-screen
|
|
551
|
+
gallery, and press-to-open.
|
|
552
|
+
|
|
553
|
+
**Give it no children unless a toolbar earns its place.** `FilesEditor` renders its bar from its
|
|
554
|
+
children and nothing without them, and select mode is reachable ONLY from that bar — so omitting
|
|
555
|
+
it removes the mode rather than stranding it. A feed of eight entries does not want eight
|
|
556
|
+
toolbars.
|
|
557
|
+
|
|
558
|
+
**Never write a tile size as a number.** The kit exports the two that exist — `THUMBNAIL_SIZE`
|
|
559
|
+
(a composer attachment, a file tile you are meant to recognise) and `COMPACT_THUMBNAIL_SIZE` (the
|
|
560
|
+
badge on a list row) — and the product uses nothing else. A literal is how a surface ends up at
|
|
561
|
+
56 or 84 for no reason anyone can reconstruct, looking subtly unlike the same surface elsewhere;
|
|
562
|
+
the complaint arrives as "why is it smaller here", which is not a question a constant can raise.
|
|
563
|
+
For a grid that should FILL its container instead, omit `itemSize` and let `minItemWidth` derive
|
|
564
|
+
the columns — that is a different decision from the tile's size, not a smaller version of it.
|
|
565
|
+
|
|
566
|
+
**Copy the component, not the layout mode.** Chat's POSTED attachments use `columns={3}
|
|
567
|
+
partialRowAlign="end"`; the `partialRowAlign` is there because a chat bubble is right-aligned, so
|
|
568
|
+
a short last row hugging the end is what lines up. A comment on a record is a left-aligned block
|
|
569
|
+
and inherits neither. Matching chat means the same component and the same size constant, not
|
|
570
|
+
transplanting a decision that belongs to a bubble.
|
|
571
|
+
|
|
572
|
+
**One list, not the documents/images split.** That split — documents as rows because a name
|
|
573
|
+
identifies them, images as tiles because content does — holds where files are the SUBJECT (a
|
|
574
|
+
record's own Files and Photos sections). An entry's files are a small mixed set of evidence
|
|
575
|
+
identified by what they are, which is the shape the fee ledger's "Supplier original" already
|
|
576
|
+
uses: one `InlineFiles` taking `application/pdf,image/*`.
|
|
577
|
+
|
|
578
|
+
**Delete is the only verb on the entry itself, and it sits in an `ActionMenu` at the TOP RIGHT.** Edit
|
|
579
|
+
stopped existing the moment the gist became a resident field — the fix that removes a control
|
|
580
|
+
beats the one that relocates it — and what remains goes where every comment surface puts it,
|
|
581
|
+
beside the byline rather than in a footer a scroll below the sentence it acts on. The menu is a
|
|
582
|
+
SIBLING of the content, so nothing nests a button inside another.
|
|
503
583
|
|
|
504
584
|
Delete belongs there DESPITE the feed being a record — because of what a multi-writer feed
|
|
505
585
|
actually produces. An extraction filing against the wrong contact and an automation
|
|
@@ -507,15 +587,38 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
507
587
|
garbage it also asks you to trust. Name what leaves with the entry in the confirmation: a
|
|
508
588
|
count somewhere else is the part nobody expects.
|
|
509
589
|
|
|
510
|
-
Only the GIST
|
|
511
|
-
a
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
590
|
+
Only the GIST is editable — everything else is derived, verbatim, an artifact or a model's
|
|
591
|
+
output, and a counterparty's own email body is a record of what THEY said for the same reason a
|
|
592
|
+
transcript is not editable. It is resident, with no mode and no verb, and it follows the value:
|
|
593
|
+
`bare` once written up so it reads as prose, `framed` while empty so it is recognisable as an
|
|
594
|
+
input. Both halves are in data_entry.md §"A value rendered TWICE is the bug".
|
|
595
|
+
|
|
596
|
+
**NO AUTHOR on an entry.** A recording is made by the people in the meeting and merely
|
|
597
|
+
transcribed by a service, so a byline reading "Recording bot" names the plumbing and fabricates
|
|
598
|
+
an author for somebody else's conversation. Once that shape is refused, showing an author on the
|
|
599
|
+
entries that happen to have one is worse than showing none — the reader learns only that we
|
|
600
|
+
attribute when we can, which is a fact about the pipeline. The record has an Owner; a customer's
|
|
601
|
+
history does not restate it once per entry. What a machine actually TOUCHED stays visible where
|
|
602
|
+
it changes how you read something: the `Inset` names an "AI summary", a transcript is labelled
|
|
603
|
+
verbatim. That is attribution of the CONTENT, which is the only kind that was load-bearing.
|
|
604
|
+
|
|
605
|
+
**The blocks SHOW; they are not folded behind a disclosure.** They are what the entry IS, and a
|
|
606
|
+
control that hides them makes the reader click to discover what the entry already says is there
|
|
607
|
+
— on every entry, forever. The fold that used to sit here was defended as "each is tall enough to
|
|
608
|
+
bury the next entry", which measurement did not support: an audio player is 54px and a file tile
|
|
609
|
+
row is 96, so hiding one behind a 20px disclosure buys 34px and costs a click. A video is the
|
|
610
|
+
honest counter-case at 358px and it still shows — it is the most informative thing on a recorded
|
|
611
|
+
call, the feed folds its own tail, and the byline and gist stay at the top of every entry, so the
|
|
612
|
+
scan survives a tall one.
|
|
613
|
+
|
|
614
|
+
**The TRANSCRIPT keeps its disclosure, and it is the only one.** It is the one block that is
|
|
615
|
+
unbounded by nature — a machine's verbatim record of half an hour — and the one nobody reads
|
|
616
|
+
straight through: you open it to check a claim the summary above made. That is what a disclosure
|
|
617
|
+
is for. An email BODY is prose of unknown length too and still shows, because it is human-written,
|
|
618
|
+
ordinarily short, and it IS the message rather than the source behind one.
|
|
516
619
|
|
|
517
620
|
**A block earns its place by carrying what nothing else on the row does.** The failure is not
|
|
518
|
-
a missing block, it is three blocks saying one thing: a participants line, a rep's
|
|
621
|
+
a missing block, it is three blocks saying one thing: a participants line, a rep's gist and a
|
|
519
622
|
machine summary all reporting that the spreadsheet owner was absent. When the summary is good
|
|
520
623
|
the rep usually adds nothing, and an entry with no note is the normal case rather than an
|
|
521
624
|
incomplete one. Cut on sight: a label above a block that already labels itself, a fact the
|