cursedbelt-core 1.0.0 → 1.1.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.
@@ -259,6 +259,24 @@ export declare function resolveKeyboardMove({ nodes, rows, row, direction, locke
259
259
  * Empty for no target — a refusal has its own words and this must never carry a second "no".
260
260
  */
261
261
  export declare function describeDrop(nodes: readonly FileTreeNode[], drop: FileTreeDrop | null): string;
262
+ /** The consumer's own words for the 🔒 access mark, per state. See {@link accessMarkCopy}. */
263
+ export interface AccessMarkCopy {
264
+ private?: string;
265
+ locked?: string;
266
+ }
267
+ /**
268
+ * What the access mark's tooltip SAYS for a given access state.
269
+ *
270
+ * 🔴 The defaults speak collections' vocabulary — "hidden from a locked page" — because
271
+ * that is the app the mark was built for. The mark itself is generic and the words are
272
+ * not: the vault's protected folders wear the same padlock meaning "everything in here
273
+ * needs the master password", and a claim of hiddenness would be false there (the rows
274
+ * are visible; their SECRETS are sealed). So the words are an argument with a default,
275
+ * resolved here rather than inline, where they can be tested without simulating a hover.
276
+ *
277
+ * Empty string for `normal` — a row with no mark has nothing to say.
278
+ */
279
+ export declare function accessMarkCopy(access: FileTreeAccess | undefined, copy?: AccessMarkCopy): string;
262
280
  /**
263
281
  * The next row whose name starts with `query`, searching from `fromIndex` and wrapping.
264
282
  *
@@ -268,6 +268,30 @@ export function describeDrop(nodes, drop) {
268
268
  const where = drop.kind === 'before' ? 'Above' : 'Below';
269
269
  return sibling ? `${where} ${sibling.name}` : `${where} this row`;
270
270
  }
271
+ /**
272
+ * What the access mark's tooltip SAYS for a given access state.
273
+ *
274
+ * 🔴 The defaults speak collections' vocabulary — "hidden from a locked page" — because
275
+ * that is the app the mark was built for. The mark itself is generic and the words are
276
+ * not: the vault's protected folders wear the same padlock meaning "everything in here
277
+ * needs the master password", and a claim of hiddenness would be false there (the rows
278
+ * are visible; their SECRETS are sealed). So the words are an argument with a default,
279
+ * resolved here rather than inline, where they can be tested without simulating a hover.
280
+ *
281
+ * Empty string for `normal` — a row with no mark has nothing to say.
282
+ */
283
+ export function accessMarkCopy(access, copy) {
284
+ if (access === 'locked')
285
+ return copy?.locked ?? 'Hidden — unlock to see what is in here';
286
+ if (access === 'private') {
287
+ // Deliberately "this or what is in it": a consumer sets `private` both for a row that
288
+ // is itself private and for a public folder holding private children (collections does
289
+ // exactly that), and a wording that claimed only the first would be false on half the
290
+ // rows it marks.
291
+ return copy?.private ?? 'Private — this, or something in it, is hidden from a locked page';
292
+ }
293
+ return '';
294
+ }
271
295
  /**
272
296
  * The next row whose name starts with `query`, searching from `fromIndex` and wrapping.
273
297
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt-core",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "Framework-agnostic core of the cursedbelt split \u2014 isomorphic domain logic. cwip below it, cursedbelt-server and cursedbelt (react) above it.",
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import {
3
+ accessMarkCopy,
3
4
  descendantIdsOf,
4
5
  type FileTreeDrop,
5
6
  type FileTreeNode,
@@ -460,3 +461,25 @@ describe('describeDrop', () => {
460
461
  expect(describeDrop(TREE, { kind: 'into', parentId: 'gone' })).toBe('Into this folder');
461
462
  });
462
463
  });
464
+
465
+ describe('accessMarkCopy', () => {
466
+ test('defaults speak collections — hiddenness, per state', () => {
467
+ expect(accessMarkCopy('locked')).toBe('Hidden — unlock to see what is in here');
468
+ expect(accessMarkCopy('private')).toContain('hidden from a locked page');
469
+ });
470
+
471
+ test('🔴 a consumer with its own privacy vocabulary overrides the words', () => {
472
+ // The vault's protected folders wear the same padlock meaning "needs the master
473
+ // password" — a claim of hiddenness would be FALSE there (rows visible, secrets
474
+ // sealed), which is why the words are an argument and not a constant.
475
+ const copy = { private: 'Protected — everything in here needs the master password' };
476
+ expect(accessMarkCopy('private', copy)).toBe(copy.private);
477
+ // An override for one state leaves the other's default standing.
478
+ expect(accessMarkCopy('locked', copy)).toBe('Hidden — unlock to see what is in here');
479
+ });
480
+
481
+ test('normal (and absent) access has nothing to say', () => {
482
+ expect(accessMarkCopy('normal')).toBe('');
483
+ expect(accessMarkCopy(undefined)).toBe('');
484
+ });
485
+ });
@@ -464,6 +464,39 @@ export function describeDrop(nodes: readonly FileTreeNode[], drop: FileTreeDrop
464
464
  return sibling ? `${where} ${sibling.name}` : `${where} this row`;
465
465
  }
466
466
 
467
+ /** The consumer's own words for the 🔒 access mark, per state. See {@link accessMarkCopy}. */
468
+ export interface AccessMarkCopy {
469
+ private?: string;
470
+ locked?: string;
471
+ }
472
+
473
+ /**
474
+ * What the access mark's tooltip SAYS for a given access state.
475
+ *
476
+ * 🔴 The defaults speak collections' vocabulary — "hidden from a locked page" — because
477
+ * that is the app the mark was built for. The mark itself is generic and the words are
478
+ * not: the vault's protected folders wear the same padlock meaning "everything in here
479
+ * needs the master password", and a claim of hiddenness would be false there (the rows
480
+ * are visible; their SECRETS are sealed). So the words are an argument with a default,
481
+ * resolved here rather than inline, where they can be tested without simulating a hover.
482
+ *
483
+ * Empty string for `normal` — a row with no mark has nothing to say.
484
+ */
485
+ export function accessMarkCopy(
486
+ access: FileTreeAccess | undefined,
487
+ copy?: AccessMarkCopy,
488
+ ): string {
489
+ if (access === 'locked') return copy?.locked ?? 'Hidden — unlock to see what is in here';
490
+ if (access === 'private') {
491
+ // Deliberately "this or what is in it": a consumer sets `private` both for a row that
492
+ // is itself private and for a public folder holding private children (collections does
493
+ // exactly that), and a wording that claimed only the first would be false on half the
494
+ // rows it marks.
495
+ return copy?.private ?? 'Private — this, or something in it, is hidden from a locked page';
496
+ }
497
+ return '';
498
+ }
499
+
467
500
  /**
468
501
  * The next row whose name starts with `query`, searching from `fromIndex` and wrapping.
469
502
  *