git-coco 0.40.0 → 0.40.1

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.
@@ -53,7 +53,7 @@ import { pathToFileURL } from 'url';
53
53
  /**
54
54
  * Current build version from package.json
55
55
  */
56
- const BUILD_VERSION = "0.40.0";
56
+ const BUILD_VERSION = "0.40.1";
57
57
 
58
58
  const isInteractive = (config) => {
59
59
  return config?.mode === 'interactive' || !!config?.interactive;
@@ -2127,11 +2127,21 @@ function formatAuthenticationError(error, logger) {
2127
2127
  logger.verbose(`\nOriginal error: ${error.message}`, { color: 'gray' });
2128
2128
  }
2129
2129
  /**
2130
- * Formats a generic error
2130
+ * Formats a generic error.
2131
+ *
2132
+ * The error message prints unconditionally (was previously gated behind
2133
+ * `--verbose`, which left users staring at a "Failed to execute command"
2134
+ * line with no actionable detail when something crashed). The full stack
2135
+ * trace stays under `logger.verbose` so plain output stays focused on the
2136
+ * one-line cause; users running into something they can't diagnose can opt
2137
+ * in with `--verbose` for the trace.
2131
2138
  */
2132
2139
  function formatGenericError(error, logger) {
2133
2140
  logger.log('\nFailed to execute command', { color: 'yellow' });
2134
- logger.verbose(`\nError: "${error.message}"`, { color: 'red' });
2141
+ logger.log(`\nError: ${error.message}`, { color: 'red' });
2142
+ if (error.stack) {
2143
+ logger.verbose(`\n${error.stack}`, { color: 'gray' });
2144
+ }
2135
2145
  }
2136
2146
  function commandExecutor(handler) {
2137
2147
  return async (argv) => {
@@ -17932,6 +17942,18 @@ function getLogInkInputEvents(state, inputValue, key = {}, context = {}) {
17932
17942
  if (key.rightArrow && state.focus === 'sidebar') {
17933
17943
  return [action({ type: 'nextSidebarTab' })];
17934
17944
  }
17945
+ // ←/→ on the inspector switch between the [Inspector] / [Actions]
17946
+ // tabs, mirroring the sidebar's left/right tab semantics. `[` and
17947
+ // `]` still work as keyboard alternatives, but the visible hint in
17948
+ // the inspector chrome shows ←/→ because the bracketed `[/]`
17949
+ // notation reads as "press the / key" — which is the global filter
17950
+ // trigger and was making users think the binding was busted.
17951
+ if (key.leftArrow && state.focus === 'detail') {
17952
+ return [action({ type: 'setInspectorTab', value: 'inspector' })];
17953
+ }
17954
+ if (key.rightArrow && state.focus === 'detail') {
17955
+ return [action({ type: 'setInspectorTab', value: 'actions' })];
17956
+ }
17935
17957
  // ←/→ on the status surface jump between the staged / unstaged /
17936
17958
  // untracked groups — the horizontal axis is "between groups", the
17937
17959
  // vertical axis (↑/↓ below) is "within the active group's files".
@@ -26778,22 +26800,39 @@ function renderHistoryInspector(h, components, state, context, _contextStatus, d
26778
26800
  h(Text, { key: 'detail-spacer-3' }, ''),
26779
26801
  h(Text, { key: 'detail-files-title' }, 'Changed files:'),
26780
26802
  ];
26803
+ // Single-cursor invariant: the file list owns the cursor when the
26804
+ // inspector tab is active; the actions list owns it when the actions
26805
+ // tab is active. Pass `focused` only for the matching tab so users
26806
+ // never see two simultaneous selection highlights inside the panel.
26807
+ const fileListFocused = focused && state.inspectorTab === 'inspector';
26781
26808
  const fileListMaxRows = Math.max(4, Math.min(detail.files.length, 10));
26782
- const fileListNodes = renderCommitFileList(h, Text, detail.files, state.selectedFileIndex, focused, fileListMaxRows, width, theme);
26783
- // Tabbed mode (#806 follow-up short terminals): render only the
26784
- // active inspector tab with a `[Inspector] Actions` header so the
26785
- // user knows what they're seeing and how to switch (`[/]` while
26786
- // focus is on the inspector). Tall terminals stack both sections
26787
- // as before.
26809
+ const fileListNodes = renderCommitFileList(h, Text, detail.files, state.selectedFileIndex, fileListFocused, fileListMaxRows, width, theme);
26810
+ // Tab indicator. Renders in BOTH tabbed (short-terminal) mode and
26811
+ // tall-stacked mode so the user can always see which tab the cursor
26812
+ // owns and learn the `[/]` toggle. Without this on tall terminals,
26813
+ // the actions list looked like a static cheat-sheet there was no
26814
+ // visible signal that the cursor could move into it.
26815
+ //
26816
+ // Spacing between tab labels comes from the labels' own padding
26817
+ // (the active label is bracketed `[Inspector]` while the inactive
26818
+ // one is space-padded ` Inspector `, so adjacency reads cleanly).
26819
+ // Earlier revisions stuck a raw `' '` between the Text children to
26820
+ // pad them visually — that crashes Ink at first paint with
26821
+ // "Text string ' ' must be rendered inside <Text> component"
26822
+ // because Box only accepts component children, never bare strings.
26823
+ const activeTab = state.inspectorTab;
26824
+ const tabHeader = h(Box, { key: 'inspector-tabs', flexDirection: 'row' }, h(Text, {
26825
+ bold: activeTab === 'inspector',
26826
+ dimColor: activeTab !== 'inspector',
26827
+ }, activeTab === 'inspector' ? '[Inspector]' : ' Inspector '), h(Text, {
26828
+ bold: activeTab === 'actions',
26829
+ dimColor: activeTab !== 'actions',
26830
+ }, activeTab === 'actions' ? '[Actions]' : ' Actions '), ...(focused
26831
+ ? [h(Text, { key: 'inspector-tabs-hint', dimColor: true }, ' · ←/→ switch')]
26832
+ : []));
26833
+ // Tabbed mode (short terminals): render only the active tab's
26834
+ // content under the tab header.
26788
26835
  if (tabbed) {
26789
- const activeTab = state.inspectorTab;
26790
- const tabHeader = h(Box, { key: 'inspector-tabs', flexDirection: 'row' }, h(Text, {
26791
- bold: activeTab === 'inspector',
26792
- dimColor: activeTab !== 'inspector',
26793
- }, activeTab === 'inspector' ? '[Inspector]' : ' Inspector '), ' ', h(Text, {
26794
- bold: activeTab === 'actions',
26795
- dimColor: activeTab !== 'actions',
26796
- }, activeTab === 'actions' ? '[Actions]' : ' Actions '));
26797
26836
  return h(Box, {
26798
26837
  borderColor: focusBorderColor(theme, focused),
26799
26838
  borderStyle: theme.borderStyle,
@@ -26807,13 +26846,16 @@ function renderHistoryInspector(h, components, state, context, _contextStatus, d
26807
26846
  cursorActive: focused,
26808
26847
  })));
26809
26848
  }
26849
+ // Tall mode: stack both sections so the user can read everything at
26850
+ // once, but show the tab header so the active section (and the
26851
+ // `[/]` switch affordance) is visible.
26810
26852
  return h(Box, {
26811
26853
  borderColor: focusBorderColor(theme, focused),
26812
26854
  borderStyle: theme.borderStyle,
26813
26855
  flexDirection: 'column',
26814
26856
  width,
26815
26857
  paddingX: 1,
26816
- }, h(Text, { bold: true }, panelTitle('Inspector', focused)), ...headerNodes, ...fileListNodes, ...renderInspectorActionsSection(h, Text, 'history-commit', width, theme, {
26858
+ }, h(Text, { bold: true }, panelTitle('Inspector', focused)), tabHeader, h(Text, { key: 'inspector-tabs-spacer' }, ''), ...headerNodes, ...fileListNodes, ...renderInspectorActionsSection(h, Text, 'history-commit', width, theme, {
26817
26859
  cursorIndex: state.inspectorActionIndex,
26818
26860
  cursorActive: focused && state.inspectorTab === 'actions',
26819
26861
  }));
package/dist/index.js CHANGED
@@ -78,7 +78,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
78
78
  /**
79
79
  * Current build version from package.json
80
80
  */
81
- const BUILD_VERSION = "0.40.0";
81
+ const BUILD_VERSION = "0.40.1";
82
82
 
83
83
  const isInteractive = (config) => {
84
84
  return config?.mode === 'interactive' || !!config?.interactive;
@@ -2152,11 +2152,21 @@ function formatAuthenticationError(error, logger) {
2152
2152
  logger.verbose(`\nOriginal error: ${error.message}`, { color: 'gray' });
2153
2153
  }
2154
2154
  /**
2155
- * Formats a generic error
2155
+ * Formats a generic error.
2156
+ *
2157
+ * The error message prints unconditionally (was previously gated behind
2158
+ * `--verbose`, which left users staring at a "Failed to execute command"
2159
+ * line with no actionable detail when something crashed). The full stack
2160
+ * trace stays under `logger.verbose` so plain output stays focused on the
2161
+ * one-line cause; users running into something they can't diagnose can opt
2162
+ * in with `--verbose` for the trace.
2156
2163
  */
2157
2164
  function formatGenericError(error, logger) {
2158
2165
  logger.log('\nFailed to execute command', { color: 'yellow' });
2159
- logger.verbose(`\nError: "${error.message}"`, { color: 'red' });
2166
+ logger.log(`\nError: ${error.message}`, { color: 'red' });
2167
+ if (error.stack) {
2168
+ logger.verbose(`\n${error.stack}`, { color: 'gray' });
2169
+ }
2160
2170
  }
2161
2171
  function commandExecutor(handler) {
2162
2172
  return async (argv) => {
@@ -17957,6 +17967,18 @@ function getLogInkInputEvents(state, inputValue, key = {}, context = {}) {
17957
17967
  if (key.rightArrow && state.focus === 'sidebar') {
17958
17968
  return [action({ type: 'nextSidebarTab' })];
17959
17969
  }
17970
+ // ←/→ on the inspector switch between the [Inspector] / [Actions]
17971
+ // tabs, mirroring the sidebar's left/right tab semantics. `[` and
17972
+ // `]` still work as keyboard alternatives, but the visible hint in
17973
+ // the inspector chrome shows ←/→ because the bracketed `[/]`
17974
+ // notation reads as "press the / key" — which is the global filter
17975
+ // trigger and was making users think the binding was busted.
17976
+ if (key.leftArrow && state.focus === 'detail') {
17977
+ return [action({ type: 'setInspectorTab', value: 'inspector' })];
17978
+ }
17979
+ if (key.rightArrow && state.focus === 'detail') {
17980
+ return [action({ type: 'setInspectorTab', value: 'actions' })];
17981
+ }
17960
17982
  // ←/→ on the status surface jump between the staged / unstaged /
17961
17983
  // untracked groups — the horizontal axis is "between groups", the
17962
17984
  // vertical axis (↑/↓ below) is "within the active group's files".
@@ -26803,22 +26825,39 @@ function renderHistoryInspector(h, components, state, context, _contextStatus, d
26803
26825
  h(Text, { key: 'detail-spacer-3' }, ''),
26804
26826
  h(Text, { key: 'detail-files-title' }, 'Changed files:'),
26805
26827
  ];
26828
+ // Single-cursor invariant: the file list owns the cursor when the
26829
+ // inspector tab is active; the actions list owns it when the actions
26830
+ // tab is active. Pass `focused` only for the matching tab so users
26831
+ // never see two simultaneous selection highlights inside the panel.
26832
+ const fileListFocused = focused && state.inspectorTab === 'inspector';
26806
26833
  const fileListMaxRows = Math.max(4, Math.min(detail.files.length, 10));
26807
- const fileListNodes = renderCommitFileList(h, Text, detail.files, state.selectedFileIndex, focused, fileListMaxRows, width, theme);
26808
- // Tabbed mode (#806 follow-up short terminals): render only the
26809
- // active inspector tab with a `[Inspector] Actions` header so the
26810
- // user knows what they're seeing and how to switch (`[/]` while
26811
- // focus is on the inspector). Tall terminals stack both sections
26812
- // as before.
26834
+ const fileListNodes = renderCommitFileList(h, Text, detail.files, state.selectedFileIndex, fileListFocused, fileListMaxRows, width, theme);
26835
+ // Tab indicator. Renders in BOTH tabbed (short-terminal) mode and
26836
+ // tall-stacked mode so the user can always see which tab the cursor
26837
+ // owns and learn the `[/]` toggle. Without this on tall terminals,
26838
+ // the actions list looked like a static cheat-sheet there was no
26839
+ // visible signal that the cursor could move into it.
26840
+ //
26841
+ // Spacing between tab labels comes from the labels' own padding
26842
+ // (the active label is bracketed `[Inspector]` while the inactive
26843
+ // one is space-padded ` Inspector `, so adjacency reads cleanly).
26844
+ // Earlier revisions stuck a raw `' '` between the Text children to
26845
+ // pad them visually — that crashes Ink at first paint with
26846
+ // "Text string ' ' must be rendered inside <Text> component"
26847
+ // because Box only accepts component children, never bare strings.
26848
+ const activeTab = state.inspectorTab;
26849
+ const tabHeader = h(Box, { key: 'inspector-tabs', flexDirection: 'row' }, h(Text, {
26850
+ bold: activeTab === 'inspector',
26851
+ dimColor: activeTab !== 'inspector',
26852
+ }, activeTab === 'inspector' ? '[Inspector]' : ' Inspector '), h(Text, {
26853
+ bold: activeTab === 'actions',
26854
+ dimColor: activeTab !== 'actions',
26855
+ }, activeTab === 'actions' ? '[Actions]' : ' Actions '), ...(focused
26856
+ ? [h(Text, { key: 'inspector-tabs-hint', dimColor: true }, ' · ←/→ switch')]
26857
+ : []));
26858
+ // Tabbed mode (short terminals): render only the active tab's
26859
+ // content under the tab header.
26813
26860
  if (tabbed) {
26814
- const activeTab = state.inspectorTab;
26815
- const tabHeader = h(Box, { key: 'inspector-tabs', flexDirection: 'row' }, h(Text, {
26816
- bold: activeTab === 'inspector',
26817
- dimColor: activeTab !== 'inspector',
26818
- }, activeTab === 'inspector' ? '[Inspector]' : ' Inspector '), ' ', h(Text, {
26819
- bold: activeTab === 'actions',
26820
- dimColor: activeTab !== 'actions',
26821
- }, activeTab === 'actions' ? '[Actions]' : ' Actions '));
26822
26861
  return h(Box, {
26823
26862
  borderColor: focusBorderColor(theme, focused),
26824
26863
  borderStyle: theme.borderStyle,
@@ -26832,13 +26871,16 @@ function renderHistoryInspector(h, components, state, context, _contextStatus, d
26832
26871
  cursorActive: focused,
26833
26872
  })));
26834
26873
  }
26874
+ // Tall mode: stack both sections so the user can read everything at
26875
+ // once, but show the tab header so the active section (and the
26876
+ // `[/]` switch affordance) is visible.
26835
26877
  return h(Box, {
26836
26878
  borderColor: focusBorderColor(theme, focused),
26837
26879
  borderStyle: theme.borderStyle,
26838
26880
  flexDirection: 'column',
26839
26881
  width,
26840
26882
  paddingX: 1,
26841
- }, h(Text, { bold: true }, panelTitle('Inspector', focused)), ...headerNodes, ...fileListNodes, ...renderInspectorActionsSection(h, Text, 'history-commit', width, theme, {
26883
+ }, h(Text, { bold: true }, panelTitle('Inspector', focused)), tabHeader, h(Text, { key: 'inspector-tabs-spacer' }, ''), ...headerNodes, ...fileListNodes, ...renderInspectorActionsSection(h, Text, 'history-commit', width, theme, {
26842
26884
  cursorIndex: state.inspectorActionIndex,
26843
26885
  cursorActive: focused && state.inspectorTab === 'actions',
26844
26886
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-coco",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "zero-effort git commits with coco.",
5
5
  "author": "gfargo <ghfargo@gmail.com>",
6
6
  "license": "MIT",
@@ -72,7 +72,7 @@
72
72
  "eslint": "^8.54.0",
73
73
  "ink-testing-library": "4.0.0",
74
74
  "jest": "^30.0.5",
75
- "release-it": "^19.0.4",
75
+ "release-it": "^20.0.1",
76
76
  "rollup": "^4.50.0",
77
77
  "rollup-plugin-dts": "^6.1.0",
78
78
  "rollup-plugin-executable": "^1.6.3",
@@ -96,7 +96,7 @@
96
96
  "@langchain/openai": "^1.4.5",
97
97
  "ajv": "^8.18.0",
98
98
  "chalk": "4.1.2",
99
- "diff": "8.0.4",
99
+ "diff": "9.0.0",
100
100
  "ini": "6.0.0",
101
101
  "ink": "7.0.1",
102
102
  "minimatch": "^10.2.4",