@franzzemen/brokenstock-shell 0.9.3 → 0.9.5

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/README.md CHANGED
@@ -15,5 +15,6 @@ Shared Angular shell library for Brokenstock web applications. Provides the foun
15
15
  - **Injection token configuration:** `SHELL_APP_NAME`, `SHELL_AUTH_URLS`, `SHELL_MENU_ITEMS`, `SHELL_COMMANDS`
16
16
  - **Content projection:** App-specific status bar items projected through `<shell-root>` into the status bar
17
17
  - **Command pattern:** `@Injectable` classes implementing `CommandDefinition`, registered via `SHELL_COMMANDS` token
18
+ - **Questions and screen keys:** `CommandService.ask` leaves a question open in the terminal (the next line answers it, Esc cancels); `ScreenKeysService` gives a screen keys of its own on its routes (Ctrl allowed, live in the terminal) — see the guide
18
19
  - **Signal-based state:** All services use Angular signals; no RxJS in public APIs
19
20
  - **Peer dependencies:** Angular 21, Material, CDK, @franzzemen/identity, @franzzemen/utility, zod, rxjs
@@ -13,7 +13,7 @@ import { gfmHeadingId } from 'marked-gfm-heading-id';
13
13
  import fm from 'front-matter';
14
14
  import { NgClass, CommonModule, DOCUMENT, isPlatformBrowser, DatePipe } from '@angular/common';
15
15
  import { Title, Meta } from '@angular/platform-browser';
16
- import { z } from 'zod/v4';
16
+ import { array, string } from 'zod/v4';
17
17
  import * as i1 from '@angular/material/icon';
18
18
  import { MatIconModule } from '@angular/material/icon';
19
19
  import * as i2 from '@angular/material/tooltip';
@@ -2606,21 +2606,59 @@ const WINDOW_KEY = 'shell.commandTerminalVisible.window';
2606
2606
  * caller could `set()` it — and whether that got remembered depended on an
2607
2607
  * effect firing somewhere else. Persisting inside the one mutator makes it
2608
2608
  * impossible to change this without persisting it.
2609
+ *
2610
+ * ⚠ A SCREEN CAN HIDE IT WITHOUT TOUCHING THE CHOICE (projects review-defects-2026-10.prd.md E21;
2611
+ * live-trading OT-19). The Trading screen hides the terminal on a phone. It used to do that with
2612
+ * `toggle()`, which saved "hidden" as the default for every new window, and put it back when the
2613
+ * screen closed. Closing the TAB never closes the screen, so the put-back never ran and the next
2614
+ * window started hidden. `hideForScreen()` hides it in this window's memory only — nothing is
2615
+ * written, so nothing outlives the tab — and returns the release. The person's ☰ still wins:
2616
+ * a toggle while a screen has it hidden shows it again, and saves nothing, because the saved
2617
+ * choice was "visible" all along.
2609
2618
  */
2610
2619
  class TerminalVisibilityService {
2611
2620
  constructor() {
2612
- this.#visible = signal(this.#readInitial(), /* @ts-ignore */
2613
- ...(ngDevMode ? [{ debugName: "#visible" }] : /* istanbul ignore next */ []));
2621
+ /** The person's choice — the one that is saved. */
2622
+ this.#choice = signal(this.#readInitial(), /* @ts-ignore */
2623
+ ...(ngDevMode ? [{ debugName: "#choice" }] : /* istanbul ignore next */ []));
2624
+ /** Screens currently hiding the terminal. Memory only, never saved. */
2625
+ this.#screenHides = signal(new Set(), /* @ts-ignore */
2626
+ ...(ngDevMode ? [{ debugName: "#screenHides" }] : /* istanbul ignore next */ []));
2614
2627
  /** Read-only on purpose — see the header. */
2615
- this.visible = this.#visible.asReadonly();
2628
+ this.visible = computed(() => this.#choice() && this.#screenHides().size === 0, /* @ts-ignore */
2629
+ ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
2616
2630
  }
2617
- #visible;
2631
+ /** The person's choice — the one that is saved. */
2632
+ #choice;
2633
+ /** Screens currently hiding the terminal. Memory only, never saved. */
2634
+ #screenHides;
2618
2635
  toggle() {
2619
- const next = !this.#visible();
2620
- this.#visible.set(next);
2636
+ if (this.#choice() && this.#screenHides().size > 0) {
2637
+ // Hidden by a screen, not by the person: ☰ shows it, and the saved choice stays "visible".
2638
+ this.#screenHides.set(new Set());
2639
+ return;
2640
+ }
2641
+ const next = !this.#choice();
2642
+ this.#choice.set(next);
2621
2643
  this.#write(sessionStorage, WINDOW_KEY, next);
2622
2644
  this.#write(localStorage, DEFAULT_KEY, next);
2623
2645
  }
2646
+ /**
2647
+ * Hides the terminal while a screen needs the room, without changing what is saved. Returns the
2648
+ * release; call it when the screen no longer needs it hidden (and on destroy). Releasing twice,
2649
+ * or after ☰ already showed it, does nothing.
2650
+ */
2651
+ hideForScreen() {
2652
+ const token = Symbol('screen-hide');
2653
+ this.#screenHides.update(s => new Set(s).add(token));
2654
+ return () => this.#screenHides.update(s => {
2655
+ if (!s.has(token))
2656
+ return s;
2657
+ const next = new Set(s);
2658
+ next.delete(token);
2659
+ return next;
2660
+ });
2661
+ }
2624
2662
  /**
2625
2663
  * This window's own choice wins; otherwise the last choice made anywhere;
2626
2664
  * otherwise visible.
@@ -2936,6 +2974,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
2936
2974
  args: [{ providedIn: 'root' }]
2937
2975
  }], ctorParameters: () => [] });
2938
2976
 
2977
+ /*
2978
+ * The only door to zod in the shell (projects review-defects-2026-10.prd.md E20, D25).
2979
+ *
2980
+ * Library files import `* as z` from HERE, never from 'zod', so call sites read as before
2981
+ * (`z.array(z.string())`) but the app's bundler sees a module re-exporting two functions and can
2982
+ * drop the rest. Importing `{z}` from 'zod/v4' hands it the whole library as one object instead:
2983
+ * 454 kB in the browser's first download in a measured test bundle, against 79 kB this way.
2984
+ *
2985
+ * A command needing another zod function adds it below; the compiler refuses anything not listed.
2986
+ * `zod-door.spec.ts` fails if any other library file imports zod directly. Specs are exempt: they
2987
+ * never reach a bundle.
2988
+ */
2989
+
2939
2990
  const FAQ_USAGE = 'faq\n' +
2940
2991
  'faq <key>\n' +
2941
2992
  'faq search <term>\n' +
@@ -2956,7 +3007,7 @@ class FaqCommand {
2956
3007
  this.usage = FAQ_USAGE;
2957
3008
  this.requiresAuth = false;
2958
3009
  this.scope = 'global';
2959
- this.argsSchema = z.array(z.string()).max(50);
3010
+ this.argsSchema = array(string()).max(50);
2960
3011
  }
2961
3012
  execute(args) {
2962
3013
  const ts = () => new Date();
@@ -3157,7 +3208,7 @@ class PendingPromptState {
3157
3208
  }
3158
3209
  }
3159
3210
 
3160
- const CommandInputSchema = z.string().trim().min(1, 'Command cannot be empty');
3211
+ const CommandInputSchema = string().trim().min(1, 'Command cannot be empty');
3161
3212
  class CommandService {
3162
3213
  #prompts;
3163
3214
  /**