magus-cli 7.3.1 → 7.4.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.
- package/package.json +4 -4
- package/src/__tests__/blind/c1-npm-package.test.ts +206 -0
- package/src/__tests__/blind/c10-colour-roles.test.ts +265 -0
- package/src/__tests__/blind/c11-cli-parse-refusal.test.ts +454 -0
- package/src/__tests__/blind/c12-skill-frontmatter.test.ts +323 -0
- package/src/__tests__/blind/c13-wrong-shape.test.ts +603 -0
- package/src/__tests__/blind/c14-cli-row-label.test.ts +259 -0
- package/src/__tests__/blind/c15-probe-ownership.test.ts +533 -0
- package/src/__tests__/blind/c16-nested-wrong-shape.test.ts +297 -0
- package/src/__tests__/blind/c17-enter-label.test.ts +625 -0
- package/src/__tests__/blind/c18-hook-shape.test.ts +354 -0
- package/src/__tests__/blind/c2-settings-parse.test.ts +350 -0
- package/src/__tests__/blind/c4-key-dispatch.test.ts +397 -0
- package/src/__tests__/blind/c5-keymap.test.ts +461 -0
- package/src/__tests__/blind/c6-request-gate.test.ts +133 -0
- package/src/__tests__/blind/c7-tab-tiers.test.ts +320 -0
- package/src/__tests__/blind/c8-text-width.test.ts +137 -0
- package/src/__tests__/blind/c9-cli-tool-version.test.ts +239 -0
- package/src/__tests__/blind/child-run.ts +114 -0
- package/src/__tests__/cli-tools-adapter.test.ts +5 -2
- package/src/__tests__/component-badge.test.ts +20 -36
- package/src/__tests__/footer-hints.test.ts +1 -1
- package/src/__tests__/manifest.test.ts +25 -17
- package/src/__tests__/marketplace-badge.test.ts +4 -4
- package/src/__tests__/models-screen-state.test.ts +36 -13
- package/src/__tests__/scope-squares.test.tsx +28 -114
- package/src/__tests__/shutdown.test.ts +578 -0
- package/src/__tests__/styles-manager.test.ts +4 -4
- package/src/__tests__/styles-screen-state.test.ts +26 -12
- package/src/__tests__/tabbar-layout.test.ts +44 -32
- package/src/__tests__/theme-adaptive-colors.test.ts +6 -59
- package/src/__tests__/tool-probe.test.ts +47 -22
- package/src/__tests__/uppercase-keybindings.test.ts +25 -12
- package/src/cli/ansi.ts +2 -2
- package/src/cli/bootstrap.ts +33 -2
- package/src/cli/doctor.ts +105 -14
- package/src/cli/install.ts +8 -2
- package/src/cli/profile.ts +18 -1
- package/src/cli/router.ts +31 -18
- package/src/cli/update-view.ts +1 -1
- package/src/data/cli-tools.ts +9 -1
- package/src/services/claude-settings.ts +151 -73
- package/src/services/cli-tool-commands.ts +15 -5
- package/src/services/cli-tool-version.ts +101 -0
- package/src/services/hook-registration.ts +23 -6
- package/src/services/manifest.ts +117 -52
- package/src/services/plugin-manager.ts +19 -5
- package/src/services/profile-adopt.ts +7 -2
- package/src/services/profile-startup.ts +87 -7
- package/src/services/profiles.ts +36 -23
- package/src/services/settings-file.ts +158 -4
- package/src/services/settings-manager.ts +19 -5
- package/src/services/skills-manager.ts +46 -0
- package/src/services/tool-probe.ts +272 -45
- package/src/services/toolchain.ts +39 -0
- package/src/services/update-engine.ts +5 -0
- package/src/services/update-plan.ts +9 -0
- package/src/tui.tsx +48 -21
- package/src/ui/App.tsx +58 -197
- package/src/ui/adapters/cliToolsAdapter.ts +141 -12
- package/src/ui/adapters/pluginsAdapter.ts +10 -7
- package/src/ui/adapters/settingsAdapter.ts +0 -21
- package/src/ui/adapters/skillsAdapter.ts +27 -8
- package/src/ui/adapters/stylesAdapter.ts +15 -11
- package/src/ui/components/CategoryHeader.tsx +7 -1
- package/src/ui/components/EmptyFilterState.tsx +2 -2
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScreenErrorBoundary.tsx +105 -0
- package/src/ui/components/TabBar.tsx +93 -21
- package/src/ui/components/layout/FooterHints.tsx +6 -27
- package/src/ui/components/layout/ScreenLayout.tsx +68 -42
- package/src/ui/components/layout/index.ts +1 -5
- package/src/ui/components/modals/ConfirmModal.tsx +2 -19
- package/src/ui/components/modals/HelpOverlay.tsx +186 -0
- package/src/ui/components/modals/MessageModal.tsx +3 -15
- package/src/ui/components/modals/ModalContainer.tsx +99 -60
- package/src/ui/components/primitives/ErrorNotice.tsx +38 -0
- package/src/ui/components/primitives/ListCategoryRow.tsx +18 -7
- package/src/ui/components/primitives/MetaText.tsx +1 -1
- package/src/ui/components/primitives/ScopeDetail.tsx +31 -26
- package/src/ui/components/primitives/ScopeSquares.tsx +23 -28
- package/src/ui/components/primitives/SelectableRow.tsx +24 -1
- package/src/ui/components/primitives/index.ts +4 -3
- package/src/ui/hooks/useProfileStartupModal.ts +15 -3
- package/src/ui/input/KeyRouter.tsx +180 -0
- package/src/ui/input/key-dispatch.ts +149 -0
- package/src/ui/keymap.ts +898 -0
- package/src/ui/renderers/cliToolRenderers.tsx +128 -66
- package/src/ui/renderers/gitignoreRenderers.tsx +41 -43
- package/src/ui/renderers/markdownRenderer.tsx +2 -2
- package/src/ui/renderers/mcpRenderers.tsx +28 -14
- package/src/ui/renderers/modelRenderers.tsx +12 -13
- package/src/ui/renderers/modelVisuals.tsx +0 -2
- package/src/ui/renderers/pluginRenderers.tsx +47 -54
- package/src/ui/renderers/profileRenderers.tsx +32 -37
- package/src/ui/renderers/settingsRenderers.tsx +84 -55
- package/src/ui/renderers/skillRenderers.tsx +237 -190
- package/src/ui/renderers/styleRenderers.tsx +13 -2
- package/src/ui/runtime/shutdown.ts +268 -0
- package/src/ui/screens/AliasScreen.tsx +118 -100
- package/src/ui/screens/CliToolsScreen.tsx +98 -87
- package/src/ui/screens/EnvVarsScreen.tsx +44 -42
- package/src/ui/screens/GitignoreScreen.tsx +50 -55
- package/src/ui/screens/McpRegistryScreen.tsx +52 -86
- package/src/ui/screens/McpScreen.tsx +47 -33
- package/src/ui/screens/ModelsScreen.tsx +59 -104
- package/src/ui/screens/PluginsScreen.tsx +123 -127
- package/src/ui/screens/ProfilesScreen.tsx +49 -46
- package/src/ui/screens/SkillsScreen.tsx +184 -204
- package/src/ui/screens/StylesScreen.tsx +104 -157
- package/src/ui/state/AppContext.tsx +6 -0
- package/src/ui/state/DimensionsContext.tsx +1 -10
- package/src/ui/state/reducer.ts +5 -8
- package/src/ui/state/types.ts +11 -9
- package/src/ui/text-width.ts +341 -0
- package/src/ui/theme-mode.ts +34 -71
- package/src/ui/theme.ts +50 -28
- package/src/utils/request-gate.ts +37 -0
- package/src/ui/components/ScopeIndicator.tsx +0 -63
- package/src/ui/components/SearchInput.tsx +0 -57
- package/src/ui/hooks/index.ts +0 -2
- package/src/ui/hooks/useKeyboard.ts +0 -24
- package/src/ui/hooks/useKeyboardHandler.ts +0 -63
|
@@ -41,7 +41,8 @@ import {
|
|
|
41
41
|
import { EmptyFilterState } from "../components/EmptyFilterState.js";
|
|
42
42
|
import { ScrollableList } from "../components/ScrollableList.js";
|
|
43
43
|
import { ScreenLayout } from "../components/layout/index.js";
|
|
44
|
-
import {
|
|
44
|
+
import { useScreenKeys, useSearchInput } from "../input/KeyRouter.js";
|
|
45
|
+
import { type BindingId, moveDirection } from "../keymap.js";
|
|
45
46
|
import {
|
|
46
47
|
renderStyleDetail,
|
|
47
48
|
renderStyleRow,
|
|
@@ -57,7 +58,7 @@ export function StylesScreen() {
|
|
|
57
58
|
const modal = useModal();
|
|
58
59
|
|
|
59
60
|
const isSearchActive =
|
|
60
|
-
state.isSearching && state.currentRoute.screen === "styles"
|
|
61
|
+
state.isSearching && state.currentRoute.screen === "styles";
|
|
61
62
|
|
|
62
63
|
// ── Data ──────────────────────────────────────────────────────────────────
|
|
63
64
|
|
|
@@ -748,120 +749,93 @@ export function StylesScreen() {
|
|
|
748
749
|
[stylesState.selectedIndex, allItems, dispatch],
|
|
749
750
|
);
|
|
750
751
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
/^[0-9]$/.test(event.name ?? "") || event.name === "tab";
|
|
764
|
-
if (stylesState.status && !isNavigation) {
|
|
765
|
-
dispatch({ type: "STYLES_STATUS_CLEAR" });
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
const hasQuery = stylesState.searchQuery.length > 0;
|
|
769
|
-
|
|
770
|
-
if (event.name === "escape") {
|
|
771
|
-
if (hasQuery || isSearchActive) {
|
|
772
|
-
dispatch({ type: "STYLES_SET_SEARCH", query: "" });
|
|
773
|
-
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
774
|
-
dispatch({ type: "STYLES_SELECT", index: 0 });
|
|
775
|
-
}
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (event.name === "backspace" || event.name === "delete") {
|
|
780
|
-
if (hasQuery) {
|
|
781
|
-
dispatch({ type: "STYLES_SEARCH_BACKSPACE" });
|
|
782
|
-
// Leaving search mode is driven by what was there BEFORE this
|
|
783
|
-
// keystroke, which is the one thing the closure does know reliably.
|
|
784
|
-
if (stylesState.searchQuery.length <= 1) {
|
|
785
|
-
dispatch({ type: "SET_SEARCHING", isSearching: false });
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
752
|
+
// The search box, while it has focus: digits, j/k, q and ? are typed here.
|
|
753
|
+
useSearchInput({
|
|
754
|
+
focused: isSearchActive,
|
|
755
|
+
query: stylesState.searchQuery,
|
|
756
|
+
append: (char) => dispatch({ type: "STYLES_SEARCH_APPEND", char }),
|
|
757
|
+
erase: () => dispatch({ type: "STYLES_SEARCH_BACKSPACE" }),
|
|
758
|
+
clear: () => {
|
|
759
|
+
dispatch({ type: "STYLES_SET_SEARCH", query: "" });
|
|
760
|
+
dispatch({ type: "STYLES_SELECT", index: 0 });
|
|
761
|
+
},
|
|
762
|
+
blur: () => dispatch({ type: "SET_SEARCHING", isSearching: false }),
|
|
763
|
+
});
|
|
790
764
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
move(1);
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
765
|
+
/**
|
|
766
|
+
* The previous message belongs to the previous ACTION, so every key this
|
|
767
|
+
* screen acts on clears it first. Keys another layer takes — a digit or Tab
|
|
768
|
+
* leaving the tab, text typed into the search box — leave it alone, so a
|
|
769
|
+
* round trip through another tab keeps it.
|
|
770
|
+
*/
|
|
771
|
+
const clearStatus = () => {
|
|
772
|
+
if (stylesState.status) dispatch({ type: "STYLES_STATUS_CLEAR" });
|
|
773
|
+
};
|
|
803
774
|
|
|
804
|
-
|
|
775
|
+
useScreenKeys("styles", {
|
|
776
|
+
toggle: () => {
|
|
777
|
+
clearStatus();
|
|
805
778
|
handleToggle();
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
//
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
} else if (event.name === "j") {
|
|
845
|
-
move(1);
|
|
846
|
-
} else if (event.name === "a") {
|
|
847
|
-
handleApply();
|
|
848
|
-
} else if (event.name === "c") {
|
|
849
|
-
handleClear();
|
|
850
|
-
} else if (event.name === "x") {
|
|
779
|
+
},
|
|
780
|
+
apply: () => {
|
|
781
|
+
clearStatus();
|
|
782
|
+
void handleApply();
|
|
783
|
+
},
|
|
784
|
+
fill: () => {
|
|
785
|
+
clearStatus();
|
|
786
|
+
void handleFillTemplate();
|
|
787
|
+
},
|
|
788
|
+
clear: () => {
|
|
789
|
+
clearStatus();
|
|
790
|
+
void handleClear();
|
|
791
|
+
},
|
|
792
|
+
// Enter opens the style for editing; Space is the ticking key. Enter
|
|
793
|
+
// reads as "go into this thing", and ticking already has a key that
|
|
794
|
+
// means exactly that and nothing else.
|
|
795
|
+
edit: () => {
|
|
796
|
+
clearStatus();
|
|
797
|
+
void handleOpen();
|
|
798
|
+
},
|
|
799
|
+
fetch: () => {
|
|
800
|
+
clearStatus();
|
|
801
|
+
void handleFetch();
|
|
802
|
+
},
|
|
803
|
+
new: () => {
|
|
804
|
+
clearStatus();
|
|
805
|
+
void handleNewTeamStyle();
|
|
806
|
+
},
|
|
807
|
+
check: () => {
|
|
808
|
+
clearStatus();
|
|
809
|
+
void handleCheckUpdates(false);
|
|
810
|
+
},
|
|
811
|
+
"check-all": () => {
|
|
812
|
+
clearStatus();
|
|
813
|
+
void handleCheckUpdates(true);
|
|
814
|
+
},
|
|
815
|
+
reset: () => {
|
|
816
|
+
clearStatus();
|
|
851
817
|
handleReset();
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
handleFillTemplate();
|
|
860
|
-
} else if (event.name === "r") {
|
|
861
|
-
fetchData();
|
|
862
|
-
} else if (event.name === "/") {
|
|
818
|
+
},
|
|
819
|
+
refresh: () => {
|
|
820
|
+
clearStatus();
|
|
821
|
+
void fetchData();
|
|
822
|
+
},
|
|
823
|
+
search: () => {
|
|
824
|
+
clearStatus();
|
|
863
825
|
dispatch({ type: "SET_SEARCHING", isSearching: true });
|
|
864
|
-
}
|
|
826
|
+
},
|
|
827
|
+
// Only while a committed filter is set; otherwise Esc is the global back.
|
|
828
|
+
"clear-filter": () => {
|
|
829
|
+
if (!stylesState.searchQuery) return false;
|
|
830
|
+
clearStatus();
|
|
831
|
+
dispatch({ type: "STYLES_SET_SEARCH", query: "" });
|
|
832
|
+
dispatch({ type: "STYLES_SELECT", index: 0 });
|
|
833
|
+
return true;
|
|
834
|
+
},
|
|
835
|
+
move: (key) => {
|
|
836
|
+
clearStatus();
|
|
837
|
+
move(moveDirection(key));
|
|
838
|
+
},
|
|
865
839
|
});
|
|
866
840
|
|
|
867
841
|
// ── Status line ───────────────────────────────────────────────────────────
|
|
@@ -887,7 +861,7 @@ export function StylesScreen() {
|
|
|
887
861
|
{statusMsg.text}
|
|
888
862
|
</text>
|
|
889
863
|
) : isFilling ? (
|
|
890
|
-
<text fg={theme.colors.
|
|
864
|
+
<text fg={theme.colors.muted}>
|
|
891
865
|
Reading the codebase to fill a template… this can take a few minutes
|
|
892
866
|
</text>
|
|
893
867
|
) : undefined;
|
|
@@ -910,9 +884,9 @@ export function StylesScreen() {
|
|
|
910
884
|
<span fg={theme.colors.info}>{` │ profile ${snapshot.profile}`}</span>
|
|
911
885
|
) : null}
|
|
912
886
|
{isApplying ? (
|
|
913
|
-
<span fg={theme.colors.
|
|
887
|
+
<span fg={theme.colors.muted}> │ applying…</span>
|
|
914
888
|
) : isFetching ? (
|
|
915
|
-
<span fg={theme.colors.
|
|
889
|
+
<span fg={theme.colors.muted}> │ talking to GitHub…</span>
|
|
916
890
|
) : errors.length > 0 ? (
|
|
917
891
|
<span fg={theme.colors.danger}> │ selection invalid</span>
|
|
918
892
|
) : syncState === "not-applied" ? (
|
|
@@ -948,41 +922,26 @@ export function StylesScreen() {
|
|
|
948
922
|
selectedItem?.kind === "style" &&
|
|
949
923
|
selectedItem.source.kind === "imported" &&
|
|
950
924
|
selectedItem.source.origin === "community";
|
|
951
|
-
const selectedHasUpdate =
|
|
952
|
-
selectedItem?.kind === "style" &&
|
|
953
|
-
selectedItem.upstream?.state === "update-available";
|
|
954
925
|
|
|
955
926
|
/**
|
|
956
|
-
* Row-sensitive
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
* grows pushes itself off the screen on a split pane. `n` and `e` stay bound
|
|
960
|
-
* on a community row; they are simply not the keys that row is about.
|
|
927
|
+
* Row-sensitive: the footer advertises the keys this row is about. `n` and
|
|
928
|
+
* `e` stay bound on a community row; they are simply not what that row is
|
|
929
|
+
* for. Everything hidden here still works and is still listed in help.
|
|
961
930
|
*/
|
|
962
|
-
const
|
|
963
|
-
? [
|
|
964
|
-
{ keys: ["Space"], label: "fetch" },
|
|
965
|
-
{ keys: ["a"], label: "apply" },
|
|
966
|
-
]
|
|
931
|
+
const rowHidden: BindingId<"styles">[] = selectedIsOffer
|
|
932
|
+
? ["edit", "new", "fill", "check", "check-all", "reset"]
|
|
967
933
|
: selectedIsCommunity
|
|
968
|
-
? [
|
|
969
|
-
{ keys: ["Space"], label: "toggle" },
|
|
970
|
-
{ keys: ["a"], label: "apply" },
|
|
971
|
-
{
|
|
972
|
-
keys: ["f"],
|
|
973
|
-
label: selectedHasUpdate ? "accept update" : "re-fetch",
|
|
974
|
-
},
|
|
975
|
-
{ keys: ["u"], label: "check" },
|
|
976
|
-
]
|
|
934
|
+
? ["new", "edit", "fill"]
|
|
977
935
|
: [
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
...(selectedIsTemplate
|
|
983
|
-
? [{ keys: ["t"], label: "fill from codebase" }]
|
|
984
|
-
: []),
|
|
936
|
+
"fetch",
|
|
937
|
+
"check",
|
|
938
|
+
"check-all",
|
|
939
|
+
...(selectedIsTemplate ? [] : ["fill" as const]),
|
|
985
940
|
];
|
|
941
|
+
const hiddenHints: BindingId<"styles">[] = [
|
|
942
|
+
...rowHidden,
|
|
943
|
+
...(stylesState.searchQuery ? [] : ["clear-filter" as const]),
|
|
944
|
+
];
|
|
986
945
|
|
|
987
946
|
return (
|
|
988
947
|
<ScreenLayout
|
|
@@ -999,24 +958,12 @@ export function StylesScreen() {
|
|
|
999
958
|
}
|
|
1000
959
|
: undefined
|
|
1001
960
|
}
|
|
1002
|
-
|
|
1003
|
-
isSearchActive
|
|
1004
|
-
? [
|
|
1005
|
-
{ keys: ["type"], label: "filter" },
|
|
1006
|
-
{ keys: ["Enter"], label: "done" },
|
|
1007
|
-
{ keys: ["Esc"], label: "clear" },
|
|
1008
|
-
]
|
|
1009
|
-
: [
|
|
1010
|
-
...rowHints,
|
|
1011
|
-
{ keys: ["c"], label: "clear" },
|
|
1012
|
-
{ keys: ["/"], label: "filter" },
|
|
1013
|
-
]
|
|
1014
|
-
}
|
|
961
|
+
footer={{ hide: hiddenHints }}
|
|
1015
962
|
listPanel={(listWidth) => (
|
|
1016
963
|
<box flexDirection="column">
|
|
1017
964
|
{stylesState.snapshot.status === "loading" && (
|
|
1018
965
|
<box paddingLeft={2}>
|
|
1019
|
-
<text fg={theme.colors.
|
|
966
|
+
<text fg={theme.colors.muted}>Reading styles…</text>
|
|
1020
967
|
</box>
|
|
1021
968
|
)}
|
|
1022
969
|
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ModalState,
|
|
8
8
|
ProgressState,
|
|
9
9
|
Route,
|
|
10
|
+
Screen,
|
|
10
11
|
} from "./types.js";
|
|
11
12
|
|
|
12
13
|
// ============================================================================
|
|
@@ -204,6 +205,11 @@ export function useModal() {
|
|
|
204
205
|
},
|
|
205
206
|
});
|
|
206
207
|
},
|
|
208
|
+
|
|
209
|
+
/** The key help overlay, with `screen`'s keys first. Esc or `?` closes it. */
|
|
210
|
+
help: (screen: Screen) => {
|
|
211
|
+
dispatch({ type: "SHOW_MODAL", modal: { type: "help", screen } });
|
|
212
|
+
},
|
|
207
213
|
};
|
|
208
214
|
}
|
|
209
215
|
|
|
@@ -27,8 +27,6 @@ interface DimensionsProviderProps {
|
|
|
27
27
|
children: React.ReactNode;
|
|
28
28
|
/** Whether progress bar is visible */
|
|
29
29
|
showProgress?: boolean;
|
|
30
|
-
/** Whether debug bar is visible */
|
|
31
|
-
showDebug?: boolean;
|
|
32
30
|
/** Whether update banner is visible */
|
|
33
31
|
showUpdateBanner?: boolean;
|
|
34
32
|
/** Number of transient banners (recovery, mismatch) currently visible */
|
|
@@ -39,7 +37,6 @@ function calculateDimensions(
|
|
|
39
37
|
columns: number,
|
|
40
38
|
rows: number,
|
|
41
39
|
showProgress: boolean,
|
|
42
|
-
showDebug: boolean,
|
|
43
40
|
showUpdateBanner: boolean,
|
|
44
41
|
transientBannerCount: number,
|
|
45
42
|
): Dimensions {
|
|
@@ -49,7 +46,6 @@ function calculateDimensions(
|
|
|
49
46
|
// Calculate total available height for ScreenLayout (includes its header, panels, footer)
|
|
50
47
|
let contentHeight = terminalHeight - APP_MARGINS;
|
|
51
48
|
if (showProgress) contentHeight -= PROGRESS_HEIGHT;
|
|
52
|
-
if (showDebug) contentHeight -= 1;
|
|
53
49
|
if (showUpdateBanner) contentHeight -= 1;
|
|
54
50
|
contentHeight -= transientBannerCount;
|
|
55
51
|
contentHeight = Math.max(10, contentHeight); // Minimum 10 lines for full layout
|
|
@@ -77,7 +73,6 @@ function calculateDimensions(
|
|
|
77
73
|
export function DimensionsProvider({
|
|
78
74
|
children,
|
|
79
75
|
showProgress = false,
|
|
80
|
-
showDebug = false,
|
|
81
76
|
showUpdateBanner = false,
|
|
82
77
|
transientBannerCount = 0,
|
|
83
78
|
}: DimensionsProviderProps) {
|
|
@@ -88,7 +83,6 @@ export function DimensionsProvider({
|
|
|
88
83
|
renderer.width,
|
|
89
84
|
renderer.height,
|
|
90
85
|
showProgress,
|
|
91
|
-
showDebug,
|
|
92
86
|
showUpdateBanner,
|
|
93
87
|
transientBannerCount,
|
|
94
88
|
),
|
|
@@ -101,21 +95,19 @@ export function DimensionsProvider({
|
|
|
101
95
|
width,
|
|
102
96
|
height,
|
|
103
97
|
showProgress,
|
|
104
|
-
showDebug,
|
|
105
98
|
showUpdateBanner,
|
|
106
99
|
transientBannerCount,
|
|
107
100
|
),
|
|
108
101
|
);
|
|
109
102
|
});
|
|
110
103
|
|
|
111
|
-
// Update when showProgress/
|
|
104
|
+
// Update when showProgress/showUpdateBanner/transientBannerCount changes
|
|
112
105
|
useEffect(() => {
|
|
113
106
|
setDimensions(
|
|
114
107
|
calculateDimensions(
|
|
115
108
|
renderer.width,
|
|
116
109
|
renderer.height,
|
|
117
110
|
showProgress,
|
|
118
|
-
showDebug,
|
|
119
111
|
showUpdateBanner,
|
|
120
112
|
transientBannerCount,
|
|
121
113
|
),
|
|
@@ -124,7 +116,6 @@ export function DimensionsProvider({
|
|
|
124
116
|
renderer.width,
|
|
125
117
|
renderer.height,
|
|
126
118
|
showProgress,
|
|
127
|
-
showDebug,
|
|
128
119
|
showUpdateBanner,
|
|
129
120
|
transientBannerCount,
|
|
130
121
|
]);
|
package/src/ui/state/reducer.ts
CHANGED
|
@@ -12,7 +12,6 @@ export const initialState: AppState = {
|
|
|
12
12
|
modal: null,
|
|
13
13
|
progress: null,
|
|
14
14
|
isSearching: false,
|
|
15
|
-
detailActive: false,
|
|
16
15
|
|
|
17
16
|
// Refresh counter for triggering data refetch
|
|
18
17
|
dataRefreshVersion: 0,
|
|
@@ -104,9 +103,8 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
104
103
|
return {
|
|
105
104
|
...state,
|
|
106
105
|
currentRoute: action.route,
|
|
107
|
-
//
|
|
106
|
+
// A search box belongs to the screen it was opened on.
|
|
108
107
|
isSearching: false,
|
|
109
|
-
detailActive: false,
|
|
110
108
|
};
|
|
111
109
|
|
|
112
110
|
// =========================================================================
|
|
@@ -771,11 +769,13 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
771
769
|
// =========================================================================
|
|
772
770
|
// Modals
|
|
773
771
|
// =========================================================================
|
|
772
|
+
// The modal key layer shields everything below it, so opening a dialog
|
|
773
|
+
// leaves a focused search box focused for when it closes.
|
|
774
774
|
case "SHOW_MODAL":
|
|
775
|
-
return { ...state, modal: action.modal
|
|
775
|
+
return { ...state, modal: action.modal };
|
|
776
776
|
|
|
777
777
|
case "HIDE_MODAL":
|
|
778
|
-
return { ...state, modal: null
|
|
778
|
+
return { ...state, modal: null };
|
|
779
779
|
|
|
780
780
|
// =========================================================================
|
|
781
781
|
// Progress
|
|
@@ -795,9 +795,6 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
795
795
|
case "SET_SEARCHING":
|
|
796
796
|
return { ...state, isSearching: action.isSearching };
|
|
797
797
|
|
|
798
|
-
case "SET_DETAIL_ACTIVE":
|
|
799
|
-
return { ...state, detailActive: action.detailActive };
|
|
800
|
-
|
|
801
798
|
// =========================================================================
|
|
802
799
|
// Data Refresh
|
|
803
800
|
// =========================================================================
|
package/src/ui/state/types.ts
CHANGED
|
@@ -131,6 +131,11 @@ export type ModalState =
|
|
|
131
131
|
| {
|
|
132
132
|
type: "loading";
|
|
133
133
|
message: string;
|
|
134
|
+
}
|
|
135
|
+
| {
|
|
136
|
+
/** The key help overlay, opened with `?`, listing `screen` first. */
|
|
137
|
+
type: "help";
|
|
138
|
+
screen: Screen;
|
|
134
139
|
};
|
|
135
140
|
|
|
136
141
|
// ============================================================================
|
|
@@ -348,12 +353,12 @@ export interface AppState {
|
|
|
348
353
|
// Global UI state
|
|
349
354
|
modal: ModalState | null;
|
|
350
355
|
progress: ProgressState | null;
|
|
356
|
+
/**
|
|
357
|
+
* The current screen's search box has focus. It decides what the box draws
|
|
358
|
+
* and whether the screen's input key layer is active; key routing itself
|
|
359
|
+
* reads no flag — see ui/input/key-dispatch.ts.
|
|
360
|
+
*/
|
|
351
361
|
isSearching: boolean;
|
|
352
|
-
// True while a screen has a focused sub-view open that owns Escape (e.g.
|
|
353
|
-
// the Alias tab's flag-detail editor). The global key handler skips its
|
|
354
|
-
// Escape→navigate branch so Escape backs out of the sub-view instead of
|
|
355
|
-
// jumping to the home tab. Mirrors how `modal`/`isSearching` gate keys.
|
|
356
|
-
detailActive: boolean;
|
|
357
362
|
|
|
358
363
|
// Refresh counter - incremented when marketplace sync completes
|
|
359
364
|
// Screens can watch this to know when to refetch data
|
|
@@ -449,12 +454,9 @@ export type AppAction =
|
|
|
449
454
|
| { type: "UPDATE_PROGRESS"; state: ProgressState }
|
|
450
455
|
| { type: "HIDE_PROGRESS" }
|
|
451
456
|
|
|
452
|
-
// Search
|
|
457
|
+
// Search box focus
|
|
453
458
|
| { type: "SET_SEARCHING"; isSearching: boolean }
|
|
454
459
|
|
|
455
|
-
// Detail sub-view state (gates global Escape→navigate)
|
|
456
|
-
| { type: "SET_DETAIL_ACTIVE"; detailActive: boolean }
|
|
457
|
-
|
|
458
460
|
// Profiles screen
|
|
459
461
|
| { type: "PROFILES_SELECT"; index: number }
|
|
460
462
|
| { type: "PROFILES_DATA_LOADING" }
|