@startsimpli/ui 0.4.161 → 0.4.163

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/ui",
3
- "version": "0.4.161",
3
+ "version": "0.4.163",
4
4
  "description": "Shared UI components package for StartSimpli applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -56,7 +56,7 @@
56
56
  "@codemirror/state": "^6.0.0",
57
57
  "@codemirror/view": "^6.0.0",
58
58
  "@startsimpli/api": "^0.5.68",
59
- "@startsimpli/auth": "^0.4.43",
59
+ "@startsimpli/auth": "^0.4.44",
60
60
  "@startsimpli/i18n": "^0.1.3"
61
61
  },
62
62
  "peerDependenciesMeta": {
@@ -152,8 +152,8 @@
152
152
  "tailwindcss": "^3.4.19",
153
153
  "tsup": "^8.5.1",
154
154
  "typescript": "^6.0.3",
155
- "@startsimpli/i18n": "0.1.3",
156
- "@startsimpli/realtime": "0.0.2"
155
+ "@startsimpli/realtime": "0.0.2",
156
+ "@startsimpli/i18n": "0.1.3"
157
157
  },
158
158
  "keywords": [
159
159
  "ui",
@@ -289,9 +289,12 @@ export function RecordActivityPanel({
289
289
  <button
290
290
  type="button"
291
291
  className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground"
292
+ title={`Only ${actorText}'s edits`}
292
293
  onClick={() => onFiltersChange(prune({ ...applied, actor: sitting.actorSub }))}
293
294
  >
294
- Only {actorText}&apos;s edits
295
+ {/* The actor's name is printed on the line below; saying it
296
+ again here doubled every card (bd startsim-spywp). */}
297
+ Only their edits
295
298
  </button>
296
299
  )}
297
300
  </div>
@@ -33,6 +33,7 @@ import { DiffViewer } from '../diff';
33
33
  import {
34
34
  canDiffFieldChange,
35
35
  classifyFieldChange,
36
+ sittingNameRow,
36
37
  type FoldedFieldChange,
37
38
  type RevisionSitting,
38
39
  } from './revision-history';
@@ -180,13 +181,14 @@ export function RevisionFields({
180
181
  onNarrowToField,
181
182
  }: RevisionFieldsProps) {
182
183
  const [open, setOpen] = useState<string | null>(null);
184
+ const nameRow = sittingNameRow(sitting);
183
185
  const entries: Array<[string, FoldedFieldChange]> = [
184
186
  ...Object.keys(sitting.changed)
185
187
  .sort()
186
188
  .map((field) => [field, sitting.changed[field]!] as [string, FoldedFieldChange]),
187
189
  // The name COLUMN, kept namespaced away from the data keys because a tenant
188
190
  // may declare a data key literally called "name".
189
- ...(sitting.name ? ([['name (title)', sitting.name]] as Array<[string, FoldedFieldChange]>) : []),
191
+ ...(nameRow ? ([['name (title)', nameRow]] as Array<[string, FoldedFieldChange]>) : []),
190
192
  ];
191
193
 
192
194
  return (
@@ -167,7 +167,7 @@ describe('RecordActivityPanel', () => {
167
167
  const onFiltersChange = jest.fn()
168
168
  const list = jest.fn().mockResolvedValue({ next: null, results: [row('1', draftA)] })
169
169
  mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={onFiltersChange} />)
170
- fireEvent.click(await screen.findByRole('button', { name: /only jurga@ogmc\.example/i }))
170
+ fireEvent.click(await screen.findByRole('button', { name: 'Only their edits' }))
171
171
  expect(onFiltersChange).toHaveBeenCalledWith({ actor: 'u-jurga' })
172
172
  })
173
173
 
@@ -177,3 +177,46 @@ describe('RecordActivityPanel', () => {
177
177
  expect(await screen.findByText(/no edits match/i)).toBeInTheDocument()
178
178
  })
179
179
  })
180
+
181
+ describe('an Activity card says each thing once (bd startsim-8udmw, startsim-spywp)', () => {
182
+ const scopeRec = { id: 's-1', type: 'scope', name: 'OGMC' }
183
+
184
+ it('a create counts the rows it lists, and the name row reads "set"', async () => {
185
+ const created = row('1', draftA, {
186
+ version: 1,
187
+ action: 'created',
188
+ metadata: {
189
+ changed: { blog: { after: 'x' }, linkedin: { after: 'y' } },
190
+ name: { before: '', after: 'Gulf logistics brief' },
191
+ },
192
+ })
193
+ const list = jest.fn().mockResolvedValue({ next: null, results: [created] })
194
+ mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={jest.fn()} />)
195
+ expect(await screen.findByText('Created the record with 3 fields')).toBeInTheDocument()
196
+ const nameRow = screen.getByRole('button', { name: /name \(title\)/ })
197
+ expect(nameRow).toHaveTextContent('set')
198
+ expect(nameRow).not.toHaveTextContent('changed')
199
+ })
200
+
201
+ it('a type whose own `name` key mirrors the record name shows one name row', async () => {
202
+ const created = row('1', scopeRec, {
203
+ version: 1,
204
+ action: 'created',
205
+ metadata: {
206
+ changed: { name: { after: 'OGMC' }, path: { after: '/ogmc' } },
207
+ name: { before: null, after: 'OGMC' },
208
+ },
209
+ })
210
+ const list = jest.fn().mockResolvedValue({ next: null, results: [created] })
211
+ mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={jest.fn()} />)
212
+ expect(await screen.findByText('Created the record with 2 fields')).toBeInTheDocument()
213
+ expect(screen.queryByRole('button', { name: /name \(title\)/ })).toBeNull()
214
+ })
215
+
216
+ it('the narrow-to-person link does not repeat the address', async () => {
217
+ const list = jest.fn().mockResolvedValue({ next: null, results: [row('1', draftA)] })
218
+ mount(<RecordActivityPanel client={{ list }} filters={{}} onFiltersChange={jest.fn()} />)
219
+ expect(await screen.findByRole('button', { name: 'Only their edits' })).toBeInTheDocument()
220
+ expect(screen.queryByText(/Only jurga@ogmc\.example's edits/)).toBeNull()
221
+ })
222
+ })
@@ -50,6 +50,7 @@ export {
50
50
  normalizeFieldChange,
51
51
  normalizeRevision,
52
52
  sittingFields,
53
+ sittingNameRow,
53
54
  sittingHeadline,
54
55
  sittingSummary,
55
56
  } from './revision-history';
@@ -457,6 +457,33 @@ export function sittingFields(sitting: RevisionSitting): string[] {
457
457
  return Object.keys(sitting.changed).sort();
458
458
  }
459
459
 
460
+ /**
461
+ * The record-name row a sitting shows, if any (bd startsim-8udmw).
462
+ *
463
+ * Two corrections over printing `sitting.name` as is:
464
+ *
465
+ * - A type may declare its own data key `name` that mirrors the record name
466
+ * (scope, client). When that key changed to the same value in the same
467
+ * sitting, the name row says it a second time, under a different verb — so
468
+ * it is dropped and the data row stands for both.
469
+ * - On a CREATE there was no name before it. The wire may still carry a
470
+ * `before` (null or blank), which classified the row "changed" on a record
471
+ * that had only just come into being; on a create it reads "set".
472
+ */
473
+ export function sittingNameRow(sitting: RevisionSitting): FoldedFieldChange | undefined {
474
+ const name = sitting.name;
475
+ if (!name) return undefined;
476
+ const dataName = sitting.changed.name;
477
+ if (dataName && dataName.hasAfter && name.hasAfter && String(dataName.after) === String(name.after)) {
478
+ return undefined;
479
+ }
480
+ if (sitting.action === 'created' && name.hasBefore) {
481
+ const { before: _before, ...rest } = name;
482
+ return { ...rest, hasBefore: false };
483
+ }
484
+ return name;
485
+ }
486
+
460
487
  /** "a", "a and b", "a, b and c" — an Oxford-free list, because these are field
461
488
  * names in a sentence and not prose. */
462
489
  export function joinNames(names: string[]): string {
@@ -495,7 +522,9 @@ export function sittingHeadline(
495
522
  : `Restored ${fields.length} fields from ${from}`;
496
523
  }
497
524
  if (sitting.action === 'created') {
498
- return fields.length ? `Created the record with ${fields.length} ${fields.length === 1 ? 'field' : 'fields'}` : 'Created the record';
525
+ // Counts the rows the reader is shown under it, name row included.
526
+ const count = fields.length + (sittingNameRow(sitting) ? 1 : 0);
527
+ return count ? `Created the record with ${count} ${count === 1 ? 'field' : 'fields'}` : 'Created the record';
499
528
  }
500
529
  if (!fields.length) return renamed ? 'Renamed the record' : 'Saved the record';
501
530
  const named =