@franzzemen/brokenstock-shell 0.4.2 → 0.4.3

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.
@@ -1913,11 +1913,30 @@ class KeyboardShortcutService {
1913
1913
  this.#document.addEventListener('keydown', (event) => {
1914
1914
  this.#handleKeydown(event);
1915
1915
  });
1916
- // Focus moves are what change `suppressed`, and they are frequent — kept
1917
- // out of the zone, with the signal written directly. A signal write marks
1918
- // its consumers dirty on its own; it does not need a change-detection pass
1919
- // of its own to be scheduled here.
1920
- const refresh = () => this.suppressed.set(this.#isSuppressed());
1916
+ // ⚠ THE WRITE IS DEFERRED, AND THAT IS NOT AN OPTIMISATION.
1917
+ //
1918
+ // `element.focus()` dispatches `focusin` SYNCHRONOUSLY. Angular calls
1919
+ // `.focus()` from template-driven code — an autofocused input, the shell's
1920
+ // terminal reclaiming focus — so this listener can run in the middle of a
1921
+ // render, and a signal write there throws NG0600, "writing to signals is
1922
+ // not allowed while Angular renders the template".
1923
+ //
1924
+ // That throw shipped: it aborted the login transition, so the sign-in POST
1925
+ // returned 200 and the app still showed "not logged in", with every
1926
+ // authenticated request that followed answering 401. Nothing in the failure
1927
+ // pointed at the keyboard.
1928
+ //
1929
+ // A microtask runs after the render completes, which is soon enough for an
1930
+ // indicator a human reads and late enough to be outside the reactive
1931
+ // context. The value is compared first so an unchanged focus move writes
1932
+ // nothing at all.
1933
+ const refresh = () => {
1934
+ queueMicrotask(() => {
1935
+ const next = this.#isSuppressed();
1936
+ if (next !== this.suppressed())
1937
+ this.suppressed.set(next);
1938
+ });
1939
+ };
1921
1940
  this.#document.addEventListener('focusin', refresh, true);
1922
1941
  this.#document.addEventListener('focusout', refresh, true);
1923
1942
  });
@@ -1925,7 +1944,13 @@ class KeyboardShortcutService {
1925
1944
  registerGroup(groupId, shortcuts) {
1926
1945
  this.#groups.set(groupId, shortcuts);
1927
1946
  this.#activeGroupId.set(groupId);
1928
- this.suppressed.set(this.#isSuppressed());
1947
+ // Same hazard as the focus listener above: a screen registers from
1948
+ // `ngOnInit`, which Angular may run inside a render.
1949
+ queueMicrotask(() => {
1950
+ const next = this.#isSuppressed();
1951
+ if (next !== this.suppressed())
1952
+ this.suppressed.set(next);
1953
+ });
1929
1954
  }
1930
1955
  unregisterGroup(groupId) {
1931
1956
  this.#groups.delete(groupId);