@mak-98/dirvi-app 0.11.0-beta.6 → 0.11.0-beta.7

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.
Files changed (38) hide show
  1. package/dist/application/create-app-api.d.ts +1 -1
  2. package/dist/application/create-app-api.d.ts.map +1 -1
  3. package/dist/application/create-app-api.js +41 -14
  4. package/dist/application/init-runtime.d.ts +24 -12
  5. package/dist/application/init-runtime.d.ts.map +1 -1
  6. package/dist/application/init-runtime.error.d.ts +53 -0
  7. package/dist/application/init-runtime.error.d.ts.map +1 -0
  8. package/dist/application/init-runtime.error.js +77 -0
  9. package/dist/application/init-runtime.js +110 -76
  10. package/dist/domain/app-input.d.ts +2 -0
  11. package/dist/domain/app-input.d.ts.map +1 -0
  12. package/dist/domain/app-input.js +1 -0
  13. package/dist/domain/initialization.d.ts +9 -8
  14. package/dist/domain/initialization.d.ts.map +1 -1
  15. package/dist/domain/node-presentation.d.ts +12 -0
  16. package/dist/domain/node-presentation.d.ts.map +1 -0
  17. package/dist/domain/node-presentation.js +1 -0
  18. package/dist/domain/shell-api.d.ts +1 -1
  19. package/dist/domain/shell-api.d.ts.map +1 -1
  20. package/dist/domain/theme.d.ts +2 -14
  21. package/dist/domain/theme.d.ts.map +1 -1
  22. package/dist/domain/theme.js +9 -15
  23. package/dist/index.d.ts +0 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/ui/App.d.ts.map +1 -1
  26. package/dist/ui/App.js +13 -11
  27. package/dist/ui/AppSetup.d.ts +1 -1
  28. package/dist/ui/AppSetup.d.ts.map +1 -1
  29. package/dist/ui/AppSetup.js +29 -22
  30. package/dist/ui/color-resolver.d.ts +1 -1
  31. package/dist/ui/color-resolver.d.ts.map +1 -1
  32. package/dist/ui/hooks/useView.d.ts +2 -2
  33. package/dist/ui/hooks/useView.d.ts.map +1 -1
  34. package/dist/ui/hooks/useView.js +3 -3
  35. package/dist/ui/view.d.ts +7 -3
  36. package/dist/ui/view.d.ts.map +1 -1
  37. package/dist/ui/view.js +76 -19
  38. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import { type SerializableKey, type AppApi } from 'dirvi-lib';
1
+ import { SerializableKey, AppApi } from 'dirvi-lib';
2
2
  import type { AppApiApplicator, AppDefinition, AppDefinitionApplicator } from 'dirvi-apis';
3
3
  /**
4
4
  * Turns a plugin-owned app definition into a host-owned runtime AppApi.
@@ -1 +1 @@
1
- {"version":3,"file":"create-app-api.d.ts","sourceRoot":"","sources":["../../src/application/create-app-api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,MAAM,EAMZ,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,gBAAgB,EAAkB,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE3G;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,uBAAuB,EAAE,uBAAuB,GAC/C,gBAAgB,CAQlB;AAED,wBAAgB,YAAY,CAC1B,EAAE,SAAS,eAAe,EAC1B,KAAK,EACL,aAAa,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAyB5D"}
1
+ {"version":3,"file":"create-app-api.d.ts","sourceRoot":"","sources":["../../src/application/create-app-api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,MAAM,EACP,MAAM,WAAW,CAAC;AAKnB,OAAO,KAAK,EAAE,gBAAgB,EAAkB,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE3G;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,uBAAuB,EAAE,uBAAuB,GAC/C,gBAAgB,CAQlB;AAED,wBAAgB,YAAY,CAC1B,EAAE,SAAS,eAAe,EAC1B,KAAK,EACL,aAAa,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAiE5D"}
@@ -1,4 +1,5 @@
1
- import { createCursorApi, createFoldsApi, createNavNodeApi, createStateApi, createTreeNodeApi, } from 'dirvi-lib';
1
+ import { createFoldMethods, createCursorMethods, createUnorderedNavMethods, createOrderedNavMethods, } from 'dirvi-lib';
2
+ import { createTreeMethods, createStateApi, } from 'dirvi-lib';
2
3
  /**
3
4
  * Turns a plugin-owned app definition into a host-owned runtime AppApi.
4
5
  *
@@ -12,22 +13,48 @@ export function createAppApiApplicator(appDefinitionApplicator) {
12
13
  });
13
14
  }
14
15
  export function createAppApi(appDefinition) {
15
- const treeNodeApi = createTreeNodeApi();
16
- const foldTreeNodeApi = createTreeNodeApi();
17
- const foldsApi = createFoldsApi(foldTreeNodeApi);
18
- const cursorApi = createCursorApi();
19
- const stateApi = createStateApi(treeNodeApi, cursorApi);
20
- const navNodeApi = createNavNodeApi(treeNodeApi, foldsApi, cursorApi, appDefinition.createNavNodeApiOptions);
16
+ const treeMethods = createTreeMethods();
17
+ const foldTreeMethods = createTreeMethods();
18
+ const foldMethods = createFoldMethods(foldTreeMethods);
19
+ const cursorMethods = createCursorMethods();
20
+ const stateApi = createStateApi(treeMethods, foldMethods, cursorMethods);
21
+ const navMethods = appDefinition.semanticOrder ?
22
+ createOrderedNavMethods(treeMethods, foldMethods, cursorMethods) :
23
+ createUnorderedNavMethods(treeMethods, foldMethods, cursorMethods);
24
+ async function loadChildren(path) {
25
+ const children = await appDefinition.loadChildren(path);
26
+ const seenIds = new Set();
27
+ return children.map((child) => {
28
+ if (seenIds.has(child.id)) {
29
+ throw new Error('Application returned duplicate sibling IDs');
30
+ }
31
+ seenIds.add(child.id);
32
+ switch (child.kind) {
33
+ case 'leaf':
34
+ return treeMethods.createLeaf(child.id, child.value);
35
+ case 'unloaded-branch':
36
+ return treeMethods.createUnloadedBranch(child.id, child.value);
37
+ default:
38
+ // Also protects against invalid output from JavaScript plugins.
39
+ throw new Error('Application returned an invalid child kind');
40
+ }
41
+ });
42
+ }
21
43
  return {
22
44
  name: appDefinition.displayName,
23
- rootId: appDefinition.rootId,
24
- createRoot: () => appDefinition.createRoot(treeNodeApi),
25
- loadBranches: (path) => appDefinition.loadChildren(treeNodeApi, path),
45
+ loadChildren: (path) => loadChildren(path),
46
+ loadRoot: async () => {
47
+ const rootData = await appDefinition.loadRoot();
48
+ const children = await loadChildren([]);
49
+ const root = treeMethods.createLoadedBranch(rootData.id, rootData.value, children);
50
+ if (root === undefined) {
51
+ throw new Error('Cannot create root: duplicate child IDs');
52
+ }
53
+ return root;
54
+ },
55
+ presentNode: (id, isBranch) => appDefinition.presentNode(id, isBranch),
26
56
  subscribeToResync: appDefinition.subscribeToResync,
27
- treeNodeApi,
28
- foldsApi,
29
- cursorApi,
30
57
  stateApi,
31
- navNodeApi,
58
+ navMethods,
32
59
  };
33
60
  }
@@ -1,28 +1,40 @@
1
1
  import type { Initialization } from '../domain/initialization.js';
2
2
  import type { PublicInitApi } from '../domain/public-init-api.js';
3
3
  /**
4
- * Collects startup configuration registrations while init.mjs is evaluated.
4
+ * A session for collecting and resolving initialization-script registrations.
5
5
  *
6
- * The application exposes `api` to the script, then calls `finish()` once
7
- * evaluation completes to seal registration and obtain the validated Config.
6
+ * Expose `api` while evaluating `init.mjs`, then call `finish()` after module
7
+ * evaluation completes. Finishing validates the collected state and returns an
8
+ * immutable-by-contract snapshot of the resolved initialization.
8
9
  */
9
10
  export type InitRuntime = {
11
+ /** The temporary API made available to `init.mjs` during evaluation. */
10
12
  readonly api: PublicInitApi;
13
+ /**
14
+ * Validates and seals this session, returning its resolved startup state.
15
+ *
16
+ * The session is sealed only after successful validation. Once sealed,
17
+ * further API calls and calls to `finish()` throw
18
+ * `InitializationAlreadyFinishedError`.
19
+ */
11
20
  readonly finish: () => Initialization;
12
21
  };
13
22
  /**
14
- * Creates an isolated startup configuration session.
23
+ * Creates an isolated session for evaluating one `init.mjs` script.
15
24
  *
16
- * The returned API is exposed to `init.mjs` while the script is evaluated.
17
- * Registration calls validate their runtime inputs, and option updates validate
18
- * the merged configuration. The session keeps its mutable state private.
25
+ * Option patches are merged with the current config and the complete result is
26
+ * validated. App and command registrations are checked as they are made.
19
27
  *
20
- * Call `finish()` after evaluating `init.mjs` to seal the session and obtain
21
- * the validated initialization. A successful call can happen only once; later
22
- * API calls and calls to `finish()` throw an error.
28
+ * The runtime keeps its working state inside the closure and exposes it only
29
+ * through `api` and `finish`. finish() returns new maps, so later changes to
30
+ * the runtime’s maps cannot alter the returned maps.
23
31
  *
24
- * @returns A fresh initialization runtime with a public registration API and
25
- * a function for sealing the session.
32
+ * If no startup app is selected explicitly, the most recently registered app
33
+ * is chosen. At least one app must be registered before the session can finish.
34
+ *
35
+ * @returns A fresh initialization API and its corresponding `finish` function.
36
+ * @throws {InitRuntimeError} If a registration is invalid, validation fails,
37
+ * no app was registered, or the session has already been finished.
26
38
  */
27
39
  export declare function createInitRuntime(): InitRuntime;
28
40
  //# sourceMappingURL=init-runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"init-runtime.d.ts","sourceRoot":"","sources":["../../src/application/init-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAIlE;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,cAAc,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CA+G/C"}
1
+ {"version":3,"file":"init-runtime.d.ts","sourceRoot":"","sources":["../../src/application/init-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAWlE;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,wEAAwE;IACxE,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAE5B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,cAAc,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAuI/C"}
@@ -0,0 +1,53 @@
1
+ /** Stable categories for errors raised while collecting startup initialization. */
2
+ export type InitRuntimeErrorCode = 'ALREADY_FINISHED' | 'INVALID_VALUE' | 'DUPLICATE_APP' | 'DUPLICATE_EX_COMMAND' | 'INVALID_EX_COMMAND_HANDLER' | 'INVALID_CONFIG' | 'INVALID_APP_PACKAGE_REFERENCE' | 'NO_APPS_REGISTERED' | 'INITIAL_APP_NOT_REGISTERED' | 'INTERNAL_INVARIANT';
3
+ /**
4
+ * Base class for errors raised by the initialization runtime.
5
+ *
6
+ * Consumers can use `code` for reliable error handling without depending on
7
+ * message text. `cause` is set when an underlying validation error is wrapped.
8
+ */
9
+ export declare class InitRuntimeError extends Error {
10
+ readonly code: InitRuntimeErrorCode;
11
+ constructor(code: InitRuntimeErrorCode, message: string, options?: ErrorOptions);
12
+ }
13
+ /** An initialization API was used after its runtime had been sealed. */
14
+ export declare class InitializationAlreadyFinishedError extends InitRuntimeError {
15
+ constructor();
16
+ }
17
+ /** A registration API received a value of the wrong type or an empty string. */
18
+ export declare class InvalidInitializationValueError extends InitRuntimeError {
19
+ constructor(label: string, expected: string);
20
+ }
21
+ /** An app ID was registered more than once. */
22
+ export declare class DuplicateAppRegistrationError extends InitRuntimeError {
23
+ constructor(id: string);
24
+ }
25
+ /** An Ex command name was registered more than once. */
26
+ export declare class DuplicateExCommandRegistrationError extends InitRuntimeError {
27
+ constructor(name: string);
28
+ }
29
+ /** An Ex command registration supplied a value that is not a handler function. */
30
+ export declare class InvalidExCommandHandlerError extends InitRuntimeError {
31
+ constructor();
32
+ }
33
+ /** The initialization script supplied options that do not resolve to valid config. */
34
+ export declare class InvalidInitializationConfigError extends InitRuntimeError {
35
+ constructor(cause: unknown);
36
+ }
37
+ /** An app registration supplied a package reference that failed validation. */
38
+ export declare class InvalidAppPackageReferenceError extends InitRuntimeError {
39
+ constructor(cause: unknown);
40
+ }
41
+ /** Initialization completed without registering any apps. */
42
+ export declare class NoAppsRegisteredError extends InitRuntimeError {
43
+ constructor();
44
+ }
45
+ /** The explicitly selected startup app was not registered. */
46
+ export declare class InitialAppNotRegisteredError extends InitRuntimeError {
47
+ constructor(id: string);
48
+ }
49
+ /** An internal invariant required by the initialization runtime was violated. */
50
+ export declare class InitRuntimeInvariantError extends InitRuntimeError {
51
+ constructor(message: string);
52
+ }
53
+ //# sourceMappingURL=init-runtime.error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-runtime.error.d.ts","sourceRoot":"","sources":["../../src/application/init-runtime.error.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,MAAM,oBAAoB,GAC5B,kBAAkB,GAClB,eAAe,GACf,eAAe,GACf,sBAAsB,GACtB,4BAA4B,GAC5B,gBAAgB,GAChB,+BAA+B,GAC/B,oBAAoB,GACpB,4BAA4B,GAC5B,oBAAoB,CAAC;AAEzB;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IAEvC,QAAQ,CAAC,IAAI,EAAE,oBAAoB;gBAA1B,IAAI,EAAE,oBAAoB,EACnC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAKzB;AAED,wEAAwE;AACxE,qBAAa,kCAAmC,SAAQ,gBAAgB;;CAOvE;AAED,gFAAgF;AAChF,qBAAa,+BAAgC,SAAQ,gBAAgB;gBACvD,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAG5C;AAED,+CAA+C;AAC/C,qBAAa,6BAA8B,SAAQ,gBAAgB;gBACrD,EAAE,EAAE,MAAM;CAMvB;AAED,wDAAwD;AACxD,qBAAa,mCAAoC,SAAQ,gBAAgB;gBAC3D,IAAI,EAAE,MAAM;CAMzB;AAED,kFAAkF;AAClF,qBAAa,4BAA6B,SAAQ,gBAAgB;;CAOjE;AAED,sFAAsF;AACtF,qBAAa,gCAAiC,SAAQ,gBAAgB;gBACxD,KAAK,EAAE,OAAO;CAO3B;AAED,+EAA+E;AAC/E,qBAAa,+BAAgC,SAAQ,gBAAgB;gBACvD,KAAK,EAAE,OAAO;CAO3B;AAED,6DAA6D;AAC7D,qBAAa,qBAAsB,SAAQ,gBAAgB;;CAO1D;AAED,8DAA8D;AAC9D,qBAAa,4BAA6B,SAAQ,gBAAgB;gBACpD,EAAE,EAAE,MAAM;CAMvB;AAED,iFAAiF;AACjF,qBAAa,yBAA0B,SAAQ,gBAAgB;gBACjD,OAAO,EAAE,MAAM;CAG5B"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Base class for errors raised by the initialization runtime.
3
+ *
4
+ * Consumers can use `code` for reliable error handling without depending on
5
+ * message text. `cause` is set when an underlying validation error is wrapped.
6
+ */
7
+ export class InitRuntimeError extends Error {
8
+ code;
9
+ constructor(code, message, options) {
10
+ super(message, options);
11
+ this.code = code;
12
+ this.name = new.target.name;
13
+ }
14
+ }
15
+ /** An initialization API was used after its runtime had been sealed. */
16
+ export class InitializationAlreadyFinishedError extends InitRuntimeError {
17
+ constructor() {
18
+ super('ALREADY_FINISHED', 'Initialization has finished. Make all registrations while init.mjs is evaluated.');
19
+ }
20
+ }
21
+ /** A registration API received a value of the wrong type or an empty string. */
22
+ export class InvalidInitializationValueError extends InitRuntimeError {
23
+ constructor(label, expected) {
24
+ super('INVALID_VALUE', `${label} ${expected}.`);
25
+ }
26
+ }
27
+ /** An app ID was registered more than once. */
28
+ export class DuplicateAppRegistrationError extends InitRuntimeError {
29
+ constructor(id) {
30
+ super('DUPLICATE_APP', `An app with ID "${id}" was registered multiple times.`);
31
+ }
32
+ }
33
+ /** An Ex command name was registered more than once. */
34
+ export class DuplicateExCommandRegistrationError extends InitRuntimeError {
35
+ constructor(name) {
36
+ super('DUPLICATE_EX_COMMAND', `An Ex command named "${name}" was registered multiple times.`);
37
+ }
38
+ }
39
+ /** An Ex command registration supplied a value that is not a handler function. */
40
+ export class InvalidExCommandHandlerError extends InitRuntimeError {
41
+ constructor() {
42
+ super('INVALID_EX_COMMAND_HANDLER', 'Ex command handler must be a function.');
43
+ }
44
+ }
45
+ /** The initialization script supplied options that do not resolve to valid config. */
46
+ export class InvalidInitializationConfigError extends InitRuntimeError {
47
+ constructor(cause) {
48
+ super('INVALID_CONFIG', `Initialization produced invalid configuration: ${getErrorMessage(cause)}`, { cause });
49
+ }
50
+ }
51
+ /** An app registration supplied a package reference that failed validation. */
52
+ export class InvalidAppPackageReferenceError extends InitRuntimeError {
53
+ constructor(cause) {
54
+ super('INVALID_APP_PACKAGE_REFERENCE', `Invalid app package reference: ${getErrorMessage(cause)}`, { cause });
55
+ }
56
+ }
57
+ /** Initialization completed without registering any apps. */
58
+ export class NoAppsRegisteredError extends InitRuntimeError {
59
+ constructor() {
60
+ super('NO_APPS_REGISTERED', 'Initialization did not register any apps. Call dirvi.app.register(id, appReference) at least once in init.mjs.');
61
+ }
62
+ }
63
+ /** The explicitly selected startup app was not registered. */
64
+ export class InitialAppNotRegisteredError extends InitRuntimeError {
65
+ constructor(id) {
66
+ super('INITIAL_APP_NOT_REGISTERED', `Startup app "${id}" has not been registered.`);
67
+ }
68
+ }
69
+ /** An internal invariant required by the initialization runtime was violated. */
70
+ export class InitRuntimeInvariantError extends InitRuntimeError {
71
+ constructor(message) {
72
+ super('INTERNAL_INVARIANT', message);
73
+ }
74
+ }
75
+ function getErrorMessage(error) {
76
+ return error instanceof Error ? error.message : String(error);
77
+ }
@@ -1,18 +1,22 @@
1
1
  import { ConfigSchema, defaultConfig, merge } from '../domain/config.js';
2
2
  import { AppPackageReference } from '../domain/app-pack-ref.js';
3
+ import { DuplicateAppRegistrationError, DuplicateExCommandRegistrationError, InitializationAlreadyFinishedError, InitRuntimeInvariantError, InvalidAppPackageReferenceError, InvalidExCommandHandlerError, InvalidInitializationConfigError, InvalidInitializationValueError, } from './init-runtime.error.js';
3
4
  /**
4
- * Creates an isolated startup configuration session.
5
+ * Creates an isolated session for evaluating one `init.mjs` script.
5
6
  *
6
- * The returned API is exposed to `init.mjs` while the script is evaluated.
7
- * Registration calls validate their runtime inputs, and option updates validate
8
- * the merged configuration. The session keeps its mutable state private.
7
+ * Option patches are merged with the current config and the complete result is
8
+ * validated. App and command registrations are checked as they are made.
9
9
  *
10
- * Call `finish()` after evaluating `init.mjs` to seal the session and obtain
11
- * the validated initialization. A successful call can happen only once; later
12
- * API calls and calls to `finish()` throw an error.
10
+ * The runtime keeps its working state inside the closure and exposes it only
11
+ * through `api` and `finish`. finish() returns new maps, so later changes to
12
+ * the runtime’s maps cannot alter the returned maps.
13
13
  *
14
- * @returns A fresh initialization runtime with a public registration API and
15
- * a function for sealing the session.
14
+ * If no startup app is selected explicitly, the most recently registered app
15
+ * is chosen. At least one app must be registered before the session can finish.
16
+ *
17
+ * @returns A fresh initialization API and its corresponding `finish` function.
18
+ * @throws {InitRuntimeError} If a registration is invalid, validation fails,
19
+ * no app was registered, or the session has already been finished.
16
20
  */
17
21
  export function createInitRuntime() {
18
22
  let config = defaultConfig;
@@ -20,98 +24,128 @@ export function createInitRuntime() {
20
24
  const registeredApps = new Map();
21
25
  const exCommands = new Map();
22
26
  let finished = false;
23
- const checkFinished = () => {
27
+ // === Error Helpers =========================================================
28
+ /**
29
+ * Rejects API use after a successful call to `finish()`.
30
+ *
31
+ * Failed calls to `finish()` do not seal the runtime, allowing the caller to
32
+ * correct registrations and try again.
33
+ */
34
+ const ensureUnfinished = () => {
24
35
  if (finished) {
25
- throw new Error('Initialization has finished. Make all initializations while init.mjs is evaluated.');
36
+ throw new InitializationAlreadyFinishedError();
26
37
  }
27
38
  };
28
- const checkNonEmptyString = (value, label) => {
39
+ /** Validates values. */
40
+ const validateNonEmptyString = (value, label) => {
29
41
  if (typeof value !== 'string') {
30
- throw new Error(`${label} must be a string.`);
42
+ throw new InvalidInitializationValueError(label, 'must be a string');
31
43
  }
32
44
  if (value.trim().length === 0) {
33
- throw new Error(`${label} must not be empty.`);
45
+ throw new InvalidInitializationValueError(label, 'must not be empty');
34
46
  }
35
47
  };
36
- return {
37
- api: {
38
- options: {
39
- set: (patch) => {
40
- checkFinished();
41
- // Validate the complete result, not merely the patch. This preserves
42
- // domain invariants even when a patch changes nested configuration.
48
+ // === Public Initialization API =============================================
49
+ const api = {
50
+ options: {
51
+ set: (patch) => {
52
+ ensureUnfinished();
53
+ try {
54
+ // Validate the merged result so patches cannot break config
55
+ // invariants outside the fields they directly update.
43
56
  config = ConfigSchema.parse(merge(config, patch));
44
- },
57
+ }
58
+ catch (cause) {
59
+ throw new InvalidInitializationConfigError(cause);
60
+ }
45
61
  },
46
- app: {
47
- register: (id, appReference) => {
48
- checkFinished();
49
- checkNonEmptyString(id, 'App ID');
50
- if (registeredApps.has(id)) {
51
- throw new Error(`An app with ID "${id}" was registered multiple times.`);
52
- }
62
+ },
63
+ app: {
64
+ register: (id, appReference) => {
65
+ ensureUnfinished();
66
+ validateNonEmptyString(id, 'App ID');
67
+ if (registeredApps.has(id)) {
68
+ throw new DuplicateAppRegistrationError(id);
69
+ }
70
+ try {
53
71
  registeredApps.set(id, AppPackageReference.parse(appReference));
54
- },
55
- initial: (appId) => {
56
- checkFinished();
57
- checkNonEmptyString(appId, 'App ID');
58
- initialAppId = appId;
59
- },
72
+ }
73
+ catch (cause) {
74
+ throw new InvalidAppPackageReferenceError(cause);
75
+ }
60
76
  },
61
- ex: {
62
- register: (name, execute) => {
63
- checkFinished();
64
- checkNonEmptyString(name, 'Ex command name');
65
- if (typeof execute !== 'function') {
66
- throw new Error('Ex command handler must be a function.');
67
- }
68
- if (exCommands.has(name)) {
69
- throw new Error(`An Ex command named "${name}" was registered multiple times.`);
70
- }
71
- exCommands.set(name, { name, execute });
72
- },
77
+ initial: (appId) => {
78
+ ensureUnfinished();
79
+ validateNonEmptyString(appId, 'App ID');
80
+ initialAppId = appId;
73
81
  },
74
82
  },
75
- finish: () => {
76
- checkFinished();
77
- if (registeredApps.size === 0) {
78
- throw new Error('Initialization did not register any apps. Call ' +
79
- 'dirvi.apps.register(id, appReference) at least once in init.mjs.');
80
- }
81
- let initial;
82
- if (initialAppId !== undefined) {
83
- if (!registeredApps.has(initialAppId)) {
84
- throw new Error(`Startup app "${initialAppId}" has not been registered.`);
83
+ ex: {
84
+ register: (name, execute) => {
85
+ ensureUnfinished();
86
+ validateNonEmptyString(name, 'Ex command name');
87
+ if (typeof execute !== 'function') {
88
+ throw new InvalidExCommandHandlerError();
85
89
  }
86
- initial = initialAppId;
87
- }
88
- else {
89
- initial = getLastRegisteredAppId(registeredApps);
90
- }
91
- finished = true;
92
- return {
93
- config,
94
- app: {
95
- initial,
96
- registered: new Map(registeredApps),
97
- },
98
- exCommands: new Map(exCommands),
99
- };
90
+ if (exCommands.has(name)) {
91
+ throw new DuplicateExCommandRegistrationError(name);
92
+ }
93
+ exCommands.set(name, { name, execute });
94
+ },
100
95
  },
101
96
  };
97
+ /**
98
+ * === FINISH ================================================================
99
+ * Resolves the startup app, seals the session, and returns its snapshot.
100
+ *
101
+ * Validation happens before sealing, so a failed finish can be retried after
102
+ * the script corrects its registrations.
103
+ * ===========================================================================
104
+ */
105
+ const finish = () => {
106
+ ensureUnfinished();
107
+ if (registeredApps.size === 0) {
108
+ throw new Error('Initialization did not register any apps. Call ' +
109
+ 'dirvi.apps.register(id, appReference) at least once in init.mjs.');
110
+ }
111
+ let initial;
112
+ if (initialAppId !== undefined) {
113
+ if (!registeredApps.has(initialAppId)) {
114
+ throw new Error(`Startup app "${initialAppId}" has not been registered.`);
115
+ }
116
+ initial = initialAppId;
117
+ }
118
+ else {
119
+ initial = getLastRegisteredAppId(registeredApps);
120
+ }
121
+ finished = true;
122
+ return {
123
+ config,
124
+ app: {
125
+ initial,
126
+ registered: new Map(registeredApps),
127
+ },
128
+ exCommands: new Map(exCommands),
129
+ };
130
+ };
131
+ return {
132
+ api,
133
+ finish,
134
+ };
102
135
  }
103
136
  /**
104
- * Returns the most recently registered app ID.
137
+ * Returns the app registered last, using `Map` insertion order.
138
+ *
139
+ * This is only the fallback when `init.mjs` does not explicitly select an
140
+ * initial app. Callers must check that the map is non-empty first.
105
141
  *
106
- * Map iteration follows registration insertion order. This is a convenience
107
- * fallback only; init.mjs should set initialAppId explicitly when startup
108
- * selection is semantically important.
142
+ * @throws {InitRuntimeInvariantError} If called with an empty map.
109
143
  */
110
144
  function getLastRegisteredAppId(apps) {
111
145
  const appIds = [...apps.keys()];
112
146
  const lastAppId = appIds.at(-1);
113
147
  if (lastAppId === undefined) {
114
- throw new Error('Internal error: Check some app is registered before searching ids.');
148
+ throw new InitRuntimeInvariantError('Expected at least one registered app when selecting the startup app.');
115
149
  }
116
150
  return lastAppId;
117
151
  }
@@ -0,0 +1,2 @@
1
+ export type AppInput = string | undefined;
2
+ //# sourceMappingURL=app-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-input.d.ts","sourceRoot":"","sources":["../../src/domain/app-input.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,23 +1,24 @@
1
1
  import { Config } from './config.js';
2
2
  import { ExCommand } from './ex-command.js';
3
3
  import { AppPackageReference } from './app-pack-ref.js';
4
- export type AppInput = string | undefined;
5
4
  /**
6
- * Object set through initialization, i.e. evaluating init.mjs.
5
+ * Resolved startup state produced by evaluating `init.mjs`.
7
6
  *
8
- * Config is shared tree-UI configuration. App selection is distinct from Config
9
- * value. Ex commands are executable startup registrations and are therefore
10
- * stored separately from Config.
11
- *
12
- * initialAppId is always the ID of one app in apps: either the app explicitly
13
- * selected by init.mjs or the most recently registered app.
7
+ * The initialization script runs before the terminal UI starts. It uses the
8
+ * temporary `dirvi` configuration facade to set shared options, register app
9
+ * packages, choose the initial app, and register Ex commands.
14
10
  */
15
11
  export type Initialization = {
12
+ /** Dirvi-wide configuration resolved from the initialization script. */
16
13
  readonly config: Readonly<Config>;
14
+ /** Registered apps and the app selected for startup. */
17
15
  readonly app: {
16
+ /** ID of the app to open initially; must be present in `registered`. */
18
17
  initial: string;
18
+ /** App package references keyed by app ID. */
19
19
  registered: ReadonlyMap<string, AppPackageReference>;
20
20
  };
21
+ /** Ex commands registered by the initialization script, keyed by name. */
21
22
  readonly exCommands: ReadonlyMap<string, ExCommand>;
22
23
  };
23
24
  //# sourceMappingURL=initialization.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"initialization.d.ts","sourceRoot":"","sources":["../../src/domain/initialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;KACtD,CAAC;IACF,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrD,CAAC"}
1
+ {"version":3,"file":"initialization.d.ts","sourceRoot":"","sources":["../../src/domain/initialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAElC,wDAAwD;IACxD,QAAQ,CAAC,GAAG,EAAE;QACZ,wEAAwE;QACxE,OAAO,EAAE,MAAM,CAAC;QAEhB,8CAA8C;QAC9C,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;KACtD,CAAC;IAEF,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CACrD,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * A semantic highlight name, not a concrete terminal color.
3
+ *
4
+ * Dot-separated names allow fallback to broader categories:
5
+ * "filesystem.directory" can fall back to "filesystem".
6
+ */
7
+ export type HighlightToken = string;
8
+ export type NodePresentation = Readonly<{
9
+ content: string;
10
+ highlight: HighlightToken;
11
+ }>;
12
+ //# sourceMappingURL=node-presentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-presentation.d.ts","sourceRoot":"","sources":["../../src/domain/node-presentation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,CAAC;CAC3B,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import type { AppInput } from './initialization.js';
1
+ import type { AppInput } from './app-input.js';
2
2
  export type ShellApi = {
3
3
  readonly openApp: (appId: string, input: AppInput) => Promise<void>;
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"shell-api.d.ts","sourceRoot":"","sources":["../../src/domain/shell-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE,CAAC"}
1
+ {"version":3,"file":"shell-api.d.ts","sourceRoot":"","sources":["../../src/domain/shell-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE,CAAC"}
@@ -1,13 +1,7 @@
1
1
  import { z } from 'zod';
2
- /**
3
- * Named ANSI foreground colors supported by Dirvi themes.
4
- *
5
- * Renderers translate these symbolic names into their own color APIs. The CLI
6
- * renderer currently maps them to terminal colors; another renderer does not
7
- * need to depend on Chalk to interpret the same theme.
8
- */
2
+ import type { Theme } from 'dirvi-apis';
3
+ /** Named ANSI colors accepted by the theme schema. */
9
4
  export declare const basicColorNames: readonly ["black", "red", "green", "yellow", "blue", "cyan", "magenta", "white", "gray", "grey", "blackBright", "redBright", "greenBright", "yellowBright", "blueBright", "cyanBright", "magentaBright", "whiteBright"];
10
- export type BasicColorName = (typeof basicColorNames)[number];
11
5
  export declare const ColorConfigSchema: z.ZodObject<{
12
6
  hex: z.ZodOptional<z.ZodString>;
13
7
  ansi256: z.ZodOptional<z.ZodNumber>;
@@ -32,7 +26,6 @@ export declare const ColorConfigSchema: z.ZodObject<{
32
26
  whiteBright: "whiteBright";
33
27
  }>>;
34
28
  }, z.core.$strip>;
35
- export type ColorConfig = z.infer<typeof ColorConfigSchema>;
36
29
  export declare const ForegroundHighlightConfigSchema: z.ZodObject<{
37
30
  fg: z.ZodObject<{
38
31
  hex: z.ZodOptional<z.ZodString>;
@@ -161,10 +154,6 @@ export declare const ForegroundBackgroundHighlightConfigSchema: z.ZodObject<{
161
154
  }>>;
162
155
  }, z.core.$strip>;
163
156
  }, z.core.$strip>;
164
- export type ForegroundHighlightConfig = z.infer<typeof ForegroundHighlightConfigSchema>;
165
- export type OptionalForegroundHighlightConfig = z.infer<typeof OptionalForegroundHighlightConfigSchema>;
166
- export type BackgroundHighlightConfig = z.infer<typeof BackgroundHighlightConfigSchema>;
167
- export type ForegroundBackgroundHighlightConfig = z.infer<typeof ForegroundBackgroundHighlightConfigSchema>;
168
157
  export declare const ThemeSchema: z.ZodObject<{
169
158
  statusBar: z.ZodDefault<z.ZodObject<{
170
159
  command: z.ZodDefault<z.ZodObject<{
@@ -531,6 +520,5 @@ export declare const ThemeSchema: z.ZodObject<{
531
520
  }, z.core.$strip>>;
532
521
  }, z.core.$strip>>;
533
522
  }, z.core.$strip>;
534
- export type Theme = z.infer<typeof ThemeSchema>;
535
523
  export declare const defaultTheme: Theme;
536
524
  //# sourceMappingURL=theme.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/domain/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,yNAsBlB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAI9D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;iBAc3B,CAAC;AAEJ,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAM5D,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;iBAE1C,CAAC;AAEH,eAAO,MAAM,uCAAuC;;;;;;;;;;;;;;;;;;;;;;;;;iBAElD,CAAC;AAEH,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;iBAE1C,CAAC;AAEH,eAAO,MAAM,yCAAyC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGpD,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAC7C,OAAO,+BAA+B,CACvC,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAC7C,OAAO,+BAA+B,CACvC,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG,CAAC,CAAC,KAAK,CACvD,OAAO,yCAAyC,CACjD,CAAC;AA6FF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8CtB,CAAC;AAEH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,eAAO,MAAM,YAAY,EAAE,KAA6B,CAAC"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/domain/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAOV,KAAK,EACN,MAAM,YAAY,CAAC;AAMpB,sDAAsD;AACtD,eAAO,MAAM,eAAe,yNAsBkB,CAAC;AAU/C,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;iBAcM,CAAC;AAOrC,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;iBAEK,CAAC;AAElD,eAAO,MAAM,uCAAuC;;;;;;;;;;;;;;;;;;;;;;;;;iBAEK,CAAC;AAE1D,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;iBAEK,CAAC;AAElD,eAAO,MAAM,yCAAyC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGK,CAAC;AA6F5D,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8CK,CAAC;AAE9B,eAAO,MAAM,YAAY,EAAE,KAA6B,CAAC"}
@@ -1,18 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  // =============================================================================
3
- // Terminal colors
3
+ // Colors: Schemas
4
4
  // =============================================================================
5
- const HexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, {
6
- message: 'Expected a #rrggbb color',
7
- });
8
- const Ansi256ColorSchema = z.number().int().min(0).max(255);
9
- /**
10
- * Named ANSI foreground colors supported by Dirvi themes.
11
- *
12
- * Renderers translate these symbolic names into their own color APIs. The CLI
13
- * renderer currently maps them to terminal colors; another renderer does not
14
- * need to depend on Chalk to interpret the same theme.
15
- */
5
+ /** Named ANSI colors accepted by the theme schema. */
16
6
  export const basicColorNames = [
17
7
  'black',
18
8
  'red',
@@ -34,6 +24,10 @@ export const basicColorNames = [
34
24
  'magentaBright',
35
25
  'whiteBright',
36
26
  ];
27
+ const HexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, {
28
+ message: 'Expected a #rrggbb color',
29
+ });
30
+ const Ansi256ColorSchema = z.number().int().min(0).max(255);
37
31
  const BasicColorSchema = z.enum(basicColorNames);
38
32
  export const ColorConfigSchema = z
39
33
  .object({
@@ -47,9 +41,9 @@ export const ColorConfigSchema = z
47
41
  message: 'Expected at least one terminal color profile',
48
42
  });
49
43
  // =============================================================================
50
- // Highlights
44
+ // Highlights: Schemas
51
45
  // =============================================================================
52
- // Note: these schemas are meant to be non-strict
46
+ // These schemas are intentionally non-strict.
53
47
  export const ForegroundHighlightConfigSchema = z.object({
54
48
  fg: ColorConfigSchema,
55
49
  });
@@ -140,7 +134,7 @@ const themeDefaults = {
140
134
  },
141
135
  };
142
136
  // =============================================================================
143
- // Theme schema
137
+ // Theme: Schema
144
138
  // =============================================================================
145
139
  export const ThemeSchema = z.object({
146
140
  statusBar: z
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export type { Theme } from './domain/theme.js';
2
1
  export type { Config } from './domain/config.js';
3
2
  export type { Initialization } from './domain/initialization.js';
4
3
  export type { PublicInitApi } from './domain/public-init-api.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,YAAY,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,YAAY,EACV,cAAc,EACd,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,YAAY,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,YAAY,EACV,cAAc,EACd,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/ui/App.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,MAAM,EAEN,cAAc,EAEd,cAAc,EAEd,OAAO,EACP,eAAe,EACf,KAAK,EAGN,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,KAAK,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,IAAI;IACnE,cAAc,EAAE,iBAAiB,CAAC;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,GAAG,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,EACvE,cAAc,EACd,QAAQ,EACR,MAAM,EACN,cAAc,EACd,YAAY,EACZ,OAAO,EACP,cAAc,EACd,cAAc,EACd,MAAM,EACN,SAAS,EACT,OAAO,GACR,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,+BAgW9B"}
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/ui/App.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,MAAM,EAEN,cAAc,EAEd,cAAc,EAEd,OAAO,EACP,eAAe,EACf,KAAK,EAGN,MAAM,WAAW,CAAC;AAGnB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,KAAK,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,IAAI;IACnE,cAAc,EAAE,iBAAiB,CAAC;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACnC,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5B,cAAc,EAAE,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1C,cAAc,EAAE,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,GAAG,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,EACvE,cAAc,EACd,QAAQ,EACR,MAAM,EACN,cAAc,EACd,YAAY,EACZ,OAAO,EACP,cAAc,EACd,cAAc,EACd,MAAM,EACN,SAAS,EACT,OAAO,GACR,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,+BAoW9B"}
package/dist/ui/App.js CHANGED
@@ -9,12 +9,14 @@ export function App({ screenRenderer, shellApi, appApi, initialization, initialS
9
9
  // see current state
10
10
  stateRef.current = state;
11
11
  const navEntry = useMemo(() => {
12
- const navigation = appApi.navNodeApi.from(state.root, state.folds);
12
+ const root = appApi.stateApi.getRoot(state);
13
+ const folds = appApi.stateApi.getFolds(state);
14
+ const navigation = appApi.navMethods.from(root, folds);
13
15
  if (navigation === undefined) {
14
16
  throw new Error('Tree-surfer state root must be a loaded branch.');
15
17
  }
16
18
  return navigation;
17
- }, [appApi.navNodeApi, state.root, state.folds]);
19
+ }, [appApi.navMethods, appApi.stateApi, state]);
18
20
  const [inputState, setInputState] = useState({
19
21
  inputMode: 'normal',
20
22
  normalBuffer: '',
@@ -31,7 +33,7 @@ export function App({ screenRenderer, shellApi, appApi, initialization, initialS
31
33
  const feedbackApi = useMemo(() => createFeedbackApi((nextFeedback) => {
32
34
  setFeedback(nextFeedback);
33
35
  }), []);
34
- const view = useView(navEntry, state, appApi.navNodeApi, appApi.cursorApi, rowCount);
36
+ const view = useView(navEntry, state, appApi.stateApi, appApi.navMethods, appApi.stateApi.cursorApi, rowCount, appApi.presentNode);
35
37
  // ===========================================================================
36
38
  // Resync Subscriptions
37
39
  // ===========================================================================
@@ -40,7 +42,7 @@ export function App({ screenRenderer, shellApi, appApi, initialization, initialS
40
42
  const unsubscribe = appApi.subscribeToResync(() => {
41
43
  const oldState = stateRef.current;
42
44
  void appApi.stateApi
43
- .resync(oldState, appApi.loadBranches)
45
+ .resync(oldState, appApi.loadChildren)
44
46
  .then((newState) => {
45
47
  if (!active) {
46
48
  return;
@@ -128,7 +130,7 @@ export function App({ screenRenderer, shellApi, appApi, initialization, initialS
128
130
  return;
129
131
  case 'loadBranchEntries': {
130
132
  void appApi
131
- .loadBranches(effect.path)
133
+ .loadChildren(effect.path)
132
134
  .then((entries) => {
133
135
  dispatch({
134
136
  kind: 'updateBranch',
@@ -147,7 +149,7 @@ export function App({ screenRenderer, shellApi, appApi, initialization, initialS
147
149
  if (navigation === null) {
148
150
  return;
149
151
  }
150
- const visibleLeavesPaths = appApi.navNodeApi.visibleLeavesPaths(navigation);
152
+ const visibleLeavesPaths = appApi.navMethods.visibleLeavesPaths(navigation);
151
153
  stdout?.(JSON.stringify({
152
154
  type: 'displayed-leaves-paths',
153
155
  paths: visibleLeavesPaths,
@@ -207,11 +209,11 @@ export function App({ screenRenderer, shellApi, appApi, initialization, initialS
207
209
  }
208
210
  // TODO: Add descriptions with `...` truncation.
209
211
  case 'listFolds': {
210
- const folds = state.folds;
211
- const foldTrees = [...appApi.foldsApi.indexes(folds)].map((index) => ({
212
+ const folds = appApi.stateApi.getFolds(state);
213
+ const foldTrees = [...appApi.stateApi.foldsApi.indexes(folds)].map((index) => ({
212
214
  index,
213
- info: appApi.foldsApi.getInfoAtIndex(folds, index),
214
- active: appApi.foldsApi.isActiveAtIndex(folds, index),
215
+ info: appApi.stateApi.foldsApi.getInfoAtIndex(folds, index),
216
+ active: appApi.stateApi.foldsApi.isActiveAtIndex(folds, index),
215
217
  }));
216
218
  const indexWidth = Math.max(...foldTrees.map(({ index }) => String(index).length));
217
219
  const lines = foldTrees.map(({ index, info, active }) => {
@@ -230,7 +232,7 @@ export function App({ screenRenderer, shellApi, appApi, initialization, initialS
230
232
  feedbackApi.addMessage(effect.message, 'error');
231
233
  return;
232
234
  }
233
- }, [appApi, effectToAction, feedbackApi, navEntry, state, stdout, initialization.app.registered, shellApi]);
235
+ }, [appApi, effectToAction, feedbackApi, navEntry, state, stdout, initialization.app.registered, shellApi, initialization.exCommands]);
234
236
  // ===========================================================================
235
237
  // Input hook
236
238
  // ===========================================================================
@@ -1,4 +1,4 @@
1
- import type { AppApi, SerializableKey } from 'dirvi-lib';
1
+ import { AppApi, SerializableKey } from 'dirvi-lib';
2
2
  import type { LoadingScreenRenderer } from './loading-screen-renderer.js';
3
3
  import type { AppScreenRenderer } from './app-screen-renderer.js';
4
4
  import { ShellApi } from '../domain/shell-api.js';
@@ -1 +1 @@
1
- {"version":3,"file":"AppSetup.d.ts","sourceRoot":"","sources":["../../src/ui/AppSetup.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAS,MAAM,WAAW,CAAC;AAOhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAI7D,KAAK,eAAe,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,IAAI;IAC1E,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,EAC5E,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,GACV,EAAE,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,+BAuFrC"}
1
+ {"version":3,"file":"AppSetup.d.ts","sourceRoot":"","sources":["../../src/ui/AppSetup.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAqB,eAAe,EAAS,MAAM,WAAW,CAAC;AAO9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAI7D,KAAK,eAAe,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,IAAI;IAC1E,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAkBF,wBAAgB,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,EAC5E,iBAAiB,EACjB,qBAAqB,EACrB,cAAc,EACd,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,GACV,EAAE,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,+BA4FrC"}
@@ -1,42 +1,49 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from 'react';
3
+ import { createTreeMethods } from 'dirvi-lib';
3
4
  import { createEffectToAction, createIntentToEffect, createReducer, } from 'dirvi-lib';
4
5
  import { App } from './App.js';
5
6
  export function AppSetup({ appScreenRenderer, loadingScreenRenderer, initialization, shellApi, appApi, stdout, clipboard, }) {
7
+ const [loadState, setLoadState] = useState();
6
8
  const [initialState, setInitialState] = useState();
7
9
  // Loading and setting initial state.
8
10
  useEffect(() => {
9
11
  let mounted = true;
10
- appApi
11
- .createRoot()
12
- .then((root) => {
13
- if (!mounted) {
14
- return;
15
- }
16
- setInitialState({
17
- root,
18
- folds: appApi.foldsApi.create(appApi.rootId, {
12
+ setLoadState({ status: 'loading', appApi });
13
+ async function initialize() {
14
+ try {
15
+ const root = await appApi.loadRoot();
16
+ if (!mounted) {
17
+ return;
18
+ }
19
+ const rootId = createTreeMethods().id(root);
20
+ const folds = appApi.stateApi.foldsApi.create(rootId, {
19
21
  name: 'default',
20
22
  description: null,
21
- }),
22
- cursor: [],
23
- });
24
- })
25
- .catch((cause) => {
26
- const nextError = cause instanceof Error ? cause : new Error(String(cause));
27
- if (!mounted) {
28
- return;
23
+ });
24
+ const initialState = appApi.stateApi.create(root, folds, { path: [] });
25
+ if (initialState === undefined) {
26
+ throw new Error('Cannot create initial app state');
27
+ }
28
+ setLoadState({ status: 'ready', appApi, initialState });
29
+ }
30
+ catch (cause) {
31
+ if (!mounted) {
32
+ return;
33
+ }
34
+ const error = cause instanceof Error ? cause : new Error(String(cause));
35
+ setLoadState({ status: 'error', appApi, error });
29
36
  }
30
- throw nextError;
31
- });
37
+ }
38
+ void initialize();
32
39
  return () => {
33
40
  mounted = false;
34
41
  };
35
42
  }, [appApi]);
36
43
  // --- Pure function deps ---
37
- const reducer = useMemo(() => createReducer(appApi.treeNodeApi, appApi.foldsApi), [appApi.treeNodeApi, appApi.foldsApi]);
38
- const intentToEffect = useMemo(() => createIntentToEffect(new Set(initialization.exCommands.keys()), appApi.stateApi, appApi.cursorApi, appApi.treeNodeApi), [appApi.stateApi, appApi.cursorApi, appApi.treeNodeApi, initialization.exCommands]);
39
- const effectToAction = useMemo(() => createEffectToAction(appApi.foldsApi, appApi.cursorApi, appApi.navNodeApi), [appApi.navNodeApi, appApi.cursorApi, appApi.foldsApi]);
44
+ const reducer = useMemo(() => createReducer(appApi.stateApi, appApi.navMethods), [appApi.stateApi, appApi.navMethods]);
45
+ const intentToEffect = useMemo(() => createIntentToEffect(new Set(initialization.exCommands.keys()), appApi.stateApi), [appApi.stateApi, initialization.exCommands]);
46
+ const effectToAction = useMemo(() => createEffectToAction(appApi.stateApi, appApi.navMethods), [appApi.navMethods, appApi.stateApi]);
40
47
  // --- JSX ---
41
48
  const LoadingScreenRenderer = loadingScreenRenderer;
42
49
  if (initialState === undefined) {
@@ -1,3 +1,3 @@
1
- import type { ColorConfig } from '../domain/theme.js';
1
+ import type { ColorConfig } from 'dirvi-apis';
2
2
  export declare function colorConfigToInkColor(colorConfig: ColorConfig, colorDepth?: number): string;
3
3
  //# sourceMappingURL=color-resolver.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"color-resolver.d.ts","sourceRoot":"","sources":["../../src/ui/color-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAuBtD,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,MAAuC,GAClD,MAAM,CA2BR"}
1
+ {"version":3,"file":"color-resolver.d.ts","sourceRoot":"","sources":["../../src/ui/color-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAuB9C,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,MAAuC,GAClD,MAAM,CA2BR"}
@@ -1,4 +1,4 @@
1
- import type { CursorApi, NavBranch, NavNodeApi, SerializableKey, State } from 'dirvi-lib';
1
+ import type { CursorMethods, NavBranch, NavMethods, PresentNode, SerializableKey, State, StateApi } from 'dirvi-lib';
2
2
  import { View } from '../view.js';
3
- export declare function useView<Id extends SerializableKey, Value>(navNode: NavBranch<Id>, state: State<Id, Value>, navNodeApi: NavNodeApi<Id, Value>, cursorApi: CursorApi<Id>, rowCount: number): View;
3
+ export declare function useView<Id extends SerializableKey, Value>(navNode: NavBranch<Id>, state: State<Id, Value>, stateApi: StateApi<Id, Value>, navNodeApi: NavMethods<Id, Value>, cursorMethods: CursorMethods<Id>, rowCount: number, presentNode: PresentNode<Id>): View;
4
4
  //# sourceMappingURL=useView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useView.d.ts","sourceRoot":"","sources":["../../../src/ui/hooks/useView.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,eAAe,EACf,KAAK,EACN,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,IAAI,EAAgB,MAAM,YAAY,CAAC;AAMhD,wBAAgB,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EACvD,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,EACtB,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,EACvB,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,EACjC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,EACxB,QAAQ,EAAE,MAAM,GACf,IAAI,CAwBN"}
1
+ {"version":3,"file":"useView.d.ts","sourceRoot":"","sources":["../../../src/ui/hooks/useView.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,UAAU,EAAE,WAAW,EACvB,eAAe,EACf,KAAK,EAAE,QAAQ,EAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,IAAI,EAAgB,MAAM,YAAY,CAAC;AAMhD,wBAAgB,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,KAAK,EACvD,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,EACtB,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,EACvB,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,EAC7B,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,EACjC,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,GAC3B,IAAI,CAyBN"}
@@ -3,15 +3,15 @@ import { View } from '../view.js';
3
3
  const STATUS_BAR_HEIGHT = 1;
4
4
  // Hooks that keeps Ref of the viewport's start, and returns View sliced to
5
5
  // only the rows that should be visible.
6
- export function useView(navNode, state, navNodeApi, cursorApi, rowCount) {
6
+ export function useView(navNode, state, stateApi, navNodeApi, cursorMethods, rowCount, presentNode) {
7
7
  const viewportStartRef = useRef(0);
8
8
  const viewportHeight = Math.max(0, rowCount - STATUS_BAR_HEIGHT);
9
9
  if (viewportHeight === 0) {
10
10
  return { rows: [] };
11
11
  }
12
- const rows = View.createRows(navNode, navNodeApi, state.cursor, cursorApi);
12
+ const rows = View.createRows(navNode, navNodeApi, stateApi.getCursor(state), cursorMethods, presentNode);
13
13
  viewportStartRef.current = viewportStart(rows, viewportHeight, viewportStartRef.current);
14
- return View.create(navNode, navNodeApi, state.cursor, cursorApi, viewportHeight, viewportStartRef.current);
14
+ return View.create(navNode, navNodeApi, stateApi.getCursor(state), cursorMethods, viewportHeight, viewportStartRef.current, presentNode);
15
15
  }
16
16
  function viewportStart(rows, viewportHeight, currentViewportStart) {
17
17
  const scrollMargin = 3;
package/dist/ui/view.d.ts CHANGED
@@ -1,23 +1,27 @@
1
- import type { Cursor, CursorApi, NavBranch, NavNodeApi, SerializableKey } from 'dirvi-lib';
1
+ import type { Cursor, HighlightToken, NavBranch, NavMethods, PresentNode, SerializableKey, CursorMethods } from 'dirvi-lib';
2
2
  type ViewRowBase = {
3
3
  id: string;
4
4
  indent: number;
5
5
  content: string;
6
6
  cursor: boolean;
7
7
  selected: boolean;
8
+ highlight: HighlightToken;
8
9
  };
9
10
  export type ViewRow = (ViewRowBase & {
10
11
  type: 'leaf';
11
12
  }) | (ViewRowBase & {
12
13
  type: 'branch';
13
14
  foldedCount: number;
15
+ }) | (ViewRowBase & {
16
+ type: 'fold';
17
+ foldedCount: number;
14
18
  });
15
19
  export type View = {
16
20
  rows: ViewRow[];
17
21
  };
18
22
  export declare const View: {
19
- createRows<Id extends SerializableKey, Value>(rootNode: NavBranch<Id>, navNodeApi: NavNodeApi<Id, Value>, cursor: Cursor<Id>, cursorApi: CursorApi<Id>): ViewRow[];
20
- create<Id extends SerializableKey, Value>(rootNode: NavBranch<Id>, navNodeApi: NavNodeApi<Id, Value>, cursor: Cursor<Id>, cursorApi: CursorApi<Id>, viewportHeight: number, viewportStart: number): View;
23
+ createRows<Id extends SerializableKey, Value>(rootNode: NavBranch<Id>, navNodeApi: NavMethods<Id, Value>, cursor: Cursor<Id>, cursorMethods: CursorMethods<Id>, presentNode: PresentNode<Id>): ViewRow[];
24
+ create<Id extends SerializableKey, Value>(rootNode: NavBranch<Id>, navNodeApi: NavMethods<Id, Value>, cursor: Cursor<Id>, cursorMethods: CursorMethods<Id>, viewportHeight: number, viewportStart: number, presentNode: PresentNode<Id>): View;
21
25
  };
22
26
  export {};
23
27
  //# sourceMappingURL=view.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/ui/view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,SAAS,EACT,SAAS,EAGT,UAAU,EACV,eAAe,EAChB,MAAM,WAAW,CAAC;AAEnB,KAAK,WAAW,GAAG;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,OAAO,GACf,CAAC,WAAW,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,GACF,CAAC,WAAW,GAAG;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAAC;AAEP,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,IAAI;eACJ,EAAE,SAAS,eAAe,EAAE,KAAK,YAChC,SAAS,CAAC,EAAE,CAAC,cACX,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,UACzB,MAAM,CAAC,EAAE,CAAC,aACP,SAAS,CAAC,EAAE,CAAC,GACvB,OAAO,EAAE;WAoBL,EAAE,SAAS,eAAe,EAAE,KAAK,YAC5B,SAAS,CAAC,EAAE,CAAC,cACX,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,UACzB,MAAM,CAAC,EAAE,CAAC,aACP,SAAS,CAAC,EAAE,CAAC,kBACR,MAAM,iBACP,MAAM,GACpB,IAAI;CAOR,CAAC"}
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/ui/view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,cAAc,EACd,SAAS,EAET,UAAU,EACV,WAAW,EACX,eAAe,EAAE,aAAa,EAC/B,MAAM,WAAW,CAAC;AAGnB,KAAK,WAAW,GAAG;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,cAAc,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,OAAO,GACf,CAAC,WAAW,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,GACF,CAAC,WAAW,GAAG;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,GACF,CAAC,WAAW,GAAG;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAAC;AAEP,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AASF,eAAO,MAAM,IAAI;eACJ,EAAE,SAAS,eAAe,EAAE,KAAK,YAChC,SAAS,CAAC,EAAE,CAAC,cACX,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,UACzB,MAAM,CAAC,EAAE,CAAC,iBACH,aAAa,CAAC,EAAE,CAAC,eACnB,WAAW,CAAC,EAAE,CAAC,GAC3B,OAAO,EAAE;WAwCL,EAAE,SAAS,eAAe,EAAE,KAAK,YAC5B,SAAS,CAAC,EAAE,CAAC,cACX,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,UACzB,MAAM,CAAC,EAAE,CAAC,iBACH,aAAa,CAAC,EAAE,CAAC,kBAChB,MAAM,iBACP,MAAM,eACR,WAAW,CAAC,EAAE,CAAC,GAC3B,IAAI;CAOR,CAAC"}
package/dist/ui/view.js CHANGED
@@ -1,17 +1,28 @@
1
+ function foldId(parentPath, firstId) {
2
+ return `fold:${JSON.stringify([...parentPath, firstId])}`;
3
+ }
1
4
  export const View = {
2
- createRows(rootNode, navNodeApi, cursor, cursorApi) {
3
- const rootCursor = [];
4
- const rootRow = viewRowForEntry(rootNode, null, cursorApi.equal(cursor, rootCursor), navNodeApi);
5
+ createRows(rootNode, navNodeApi, cursor, cursorMethods, presentNode) {
6
+ const rootCursor = { path: [] };
7
+ const inlineFoldIds = new Set();
8
+ if (rootNode.children !== null) {
9
+ for (const position of navNodeApi.cursors(rootNode.children)) {
10
+ if (cursorMethods.isFoldCursor(position)) {
11
+ inlineFoldIds.add(foldId(position.parentPath, position.firstId));
12
+ }
13
+ }
14
+ }
15
+ const rootRow = viewRowForEntry(rootNode, null, cursorMethods.equal(cursor, rootCursor), navNodeApi, presentNode, inlineFoldIds);
5
16
  if (rootNode.children === null) {
6
17
  return [rootRow];
7
18
  }
8
19
  return [
9
20
  rootRow,
10
- ...viewRowsAtNode(rootNode.children, [], navNodeApi, cursor, cursorApi),
21
+ ...viewRowsAtNode(rootNode.children, [], navNodeApi, cursor, cursorMethods, presentNode, inlineFoldIds),
11
22
  ];
12
23
  },
13
- create(rootNode, navNodeApi, cursor, cursorApi, viewportHeight, viewportStart) {
14
- const rows = View.createRows(rootNode, navNodeApi, cursor, cursorApi);
24
+ create(rootNode, navNodeApi, cursor, cursorMethods, viewportHeight, viewportStart, presentNode) {
25
+ const rows = View.createRows(rootNode, navNodeApi, cursor, cursorMethods, presentNode);
15
26
  return {
16
27
  rows: rows.slice(viewportStart, viewportStart + viewportHeight),
17
28
  };
@@ -20,52 +31,98 @@ export const View = {
20
31
  /**
21
32
  * Converts one NavNode and its descendants into display ViewRows.
22
33
  *
23
- * `node` is the directory currently being visited.
24
- *
25
34
  * `parentPath` is the path of that directory. For example:
26
35
  *
27
36
  * [] root directory
28
37
  * ['src'] root/src
29
38
  * ['src', 'lib'] root/src/lib
30
39
  */
31
- function viewRowsAtNode(node, parentPath, navNodeApi, cursor, cursorApi) {
40
+ function viewRowsAtNode(node, parentPath, navNodeApi, cursor, cursorMethods, presentNode, inlineFoldIds) {
32
41
  const rows = [];
33
42
  for (const entry of node.entries) {
34
- const entryCursor = [...parentPath, entry.id];
35
- const isCursor = cursorApi.equal(cursor, entryCursor);
36
- rows.push(viewRowForEntry(entry, parentPath, isCursor, navNodeApi));
43
+ if (navNodeApi.entryIsFold(entry)) {
44
+ const firstEntry = entry.folded[0];
45
+ // Match navigation's guard for empty or richer future fold contents.
46
+ if (firstEntry === undefined || navNodeApi.entryIsFold(firstEntry)) {
47
+ continue;
48
+ }
49
+ const id = foldId(parentPath, firstEntry.id);
50
+ // Unordered summaries belong to the parent, not separate rows.
51
+ if (!inlineFoldIds.has(id)) {
52
+ continue;
53
+ }
54
+ const entryCursor = {
55
+ parentPath: [...parentPath],
56
+ firstId: firstEntry.id,
57
+ };
58
+ // Reuse the first hidden entry's highlight until folds have a presenter.
59
+ const { highlight } = presentNode(firstEntry.id, navNodeApi.entryIsBranch(firstEntry));
60
+ rows.push({
61
+ id,
62
+ indent: parentPath.length + 1,
63
+ content: `… ${entry.folded.length} folded`,
64
+ cursor: cursorMethods.equal(cursor, entryCursor),
65
+ selected: false,
66
+ highlight,
67
+ type: 'fold',
68
+ foldedCount: entry.folded.length,
69
+ });
70
+ // Hidden descendants are never rendered separately.
71
+ continue;
72
+ }
73
+ const entryCursor = {
74
+ path: [...parentPath, entry.id],
75
+ };
76
+ const isCursor = cursorMethods.equal(cursor, entryCursor);
77
+ rows.push(viewRowForEntry(entry, parentPath, isCursor, navNodeApi, presentNode, inlineFoldIds));
37
78
  // Case where there's no branches
38
79
  if (!navNodeApi.entryIsBranch(entry) || entry.children === null) {
39
80
  continue;
40
81
  }
41
- rows.push(...viewRowsAtNode(entry.children, [...parentPath, entry.id], navNodeApi, cursor, cursorApi));
82
+ rows.push(...viewRowsAtNode(entry.children, [...parentPath, entry.id], navNodeApi, cursor, cursorMethods, presentNode, inlineFoldIds));
42
83
  }
43
84
  return rows;
44
85
  }
45
- function viewRowForEntry(entry, parentPath, cursor, navNodeApi) {
86
+ function viewRowForEntry(entry, parentPath, cursor, navNodeApi, presentNode, inlineFoldIds) {
46
87
  const id = entryId(parentPath ?? [], entry.id);
47
88
  const indent = parentPath ? parentPath.length + 1 : 0;
48
89
  const selected = false;
49
90
  if (navNodeApi.entryIsBranch(entry)) {
50
- const foldedCount = entry.children === null || entry.children.folded === null
51
- ? 0
52
- : entry.children.folded.entries.length;
91
+ const { content, highlight } = presentNode(entry.id, true);
92
+ const entryPath = parentPath === null ? [] : [...parentPath, entry.id];
93
+ const foldedCount = entry.children?.entries.reduce((count, entry) => {
94
+ if (!navNodeApi.entryIsFold(entry)) {
95
+ return count;
96
+ }
97
+ const firstEntry = entry.folded[0];
98
+ if (firstEntry === undefined || navNodeApi.entryIsFold(firstEntry)) {
99
+ return count;
100
+ }
101
+ // Ordered folds already have their own rows.
102
+ if (inlineFoldIds.has(foldId(entryPath, firstEntry.id))) {
103
+ return count;
104
+ }
105
+ return count + entry.folded.length;
106
+ }, 0) ?? 0;
53
107
  return {
54
108
  id,
55
109
  indent,
56
110
  selected,
57
111
  cursor,
58
- content: `${String(entry.id)}/`,
112
+ content: content,
113
+ highlight,
59
114
  type: 'branch',
60
115
  foldedCount,
61
116
  };
62
117
  }
118
+ const { content, highlight } = presentNode(entry.id, false);
63
119
  return {
64
120
  id,
65
121
  indent,
66
122
  selected,
67
123
  cursor,
68
- content: String(entry.id),
124
+ content,
125
+ highlight,
69
126
  type: 'leaf',
70
127
  };
71
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mak-98/dirvi-app",
3
- "version": "0.11.0-beta.6",
3
+ "version": "0.11.0-beta.7",
4
4
  "description": "Shared React application logic for dirvi hosts.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -16,8 +16,8 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
- "dirvi-apis": "0.11.0-beta.6",
20
- "dirvi-lib": "0.11.0-beta.6",
19
+ "dirvi-apis": "0.11.0-beta.7",
20
+ "dirvi-lib": "0.11.0-beta.7",
21
21
  "zod": "^4.4.3"
22
22
  },
23
23
  "peerDependencies": {