aidevops 3.29.18 → 3.29.19

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
@@ -58,7 +58,7 @@ The result: an AI operations platform that manages projects across every busines
58
58
  [![Copyright](https://img.shields.io/badge/Copyright-Marcus%20Quinn%202025--2026-blue.svg)](https://github.com/marcusquinn)
59
59
 
60
60
  <!-- Release & Version Info -->
61
- [![Version](https://img.shields.io/badge/Version-3.29.18-blue.svg)](https://github.com/marcusquinn/aidevops/releases)
61
+ [![Version](https://img.shields.io/badge/Version-3.29.19-blue.svg)](https://github.com/marcusquinn/aidevops/releases)
62
62
  [![npm version](https://img.shields.io/npm/v/aidevops)](https://www.npmjs.com/package/aidevops)
63
63
  [![Homebrew](https://img.shields.io/badge/homebrew-marcusquinn%2Ftap-orange)](https://github.com/marcusquinn/homebrew-tap)
64
64
  [![GitHub repository](https://img.shields.io/badge/github-repository-181717.svg?logo=github)](https://github.com/marcusquinn/aidevops)
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.29.18
1
+ 3.29.19
package/aidevops.sh CHANGED
@@ -5,7 +5,7 @@
5
5
  # AI DevOps Framework CLI
6
6
  # Usage: aidevops <command> [options]
7
7
  #
8
- # Version: 3.29.18
8
+ # Version: 3.29.19
9
9
 
10
10
  set -euo pipefail
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "3.29.18",
3
+ "version": "3.29.19",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -4,13 +4,9 @@ import { MachineRail, Sidebar } from "./AppNavigation";
4
4
  import { Workspace } from "./AppWorkspace";
5
5
  import type { ConversationMode, FontPreference, FontSizePreference, ShellMode, SurfaceId, ThemePreference } from "./app-model";
6
6
  import { DEFAULT_ACCENT_HUE, DEFAULT_FONT, DEFAULT_FONT_SIZE, fileRootBySurface, findSurface, findSurfaceSectionLabel, fontFamilyForPreference, fontSizeForPreference, getSystemTheme, isFontPreference, isFontSizePreference } from "./app-model";
7
+ import { type NavigationHistory, nextHistoryIndex, useNavigationHistoryKeyboard } from "./navigation-history";
7
8
  import { fetchStatus, mockedStatus } from "./status-client";
8
9
 
9
- interface NavigationHistory {
10
- entries: SurfaceId[];
11
- index: number;
12
- }
13
-
14
10
  interface WebKitBridgeWindow extends Window {
15
11
  webkit?: {
16
12
  messageHandlers?: {
@@ -37,7 +33,7 @@ export const appearanceStorageKeys = {
37
33
  } as const;
38
34
 
39
35
  const defaultSidebarWidth = 302;
40
- const minSidebarWidth = 248;
36
+ const minSidebarWidth = 300;
41
37
  const maxSidebarWidth = 520;
42
38
  export const loadingSkeletonPanelLabels = ["machine rail", "sidebar", "workspace", "status bar"] as const;
43
39
  export const loadingBrandGlyph = ">_";
@@ -93,6 +89,7 @@ export function App(): ReactElement {
93
89
  const canGoBack = navigation.index > 0;
94
90
  const canGoForward = navigation.index < navigation.entries.length - 1;
95
91
  const fileRoot = fileRootBySurface[activeSurface];
92
+ useNavigationHistoryKeyboard(setNavigation);
96
93
 
97
94
  const setActiveSurface = (surface: SurfaceId) => {
98
95
  setNavigation((current) => {
@@ -108,11 +105,11 @@ export function App(): ReactElement {
108
105
  };
109
106
 
110
107
  const goBack = () => {
111
- setNavigation((current) => ({ ...current, index: Math.max(0, current.index - 1) }));
108
+ setNavigation((current) => ({ ...current, index: nextHistoryIndex(current, -1) }));
112
109
  };
113
110
 
114
111
  const goForward = () => {
115
- setNavigation((current) => ({ ...current, index: Math.min(current.entries.length - 1, current.index + 1) }));
112
+ setNavigation((current) => ({ ...current, index: nextHistoryIndex(current, 1) }));
116
113
  };
117
114
 
118
115
  useEffect(() => {
@@ -145,7 +145,7 @@ export function Sidebar({ activeSurface, accentHue, conversationMode, fontPrefer
145
145
 
146
146
  return (
147
147
  <aside className="app-sidebar" aria-label={text.navigationLabel}>
148
- <SidebarHeader setShellMode={setShellMode} shellMode={shellMode} />
148
+ <SidebarHeader setActiveSurface={setActiveSurface} setShellMode={setShellMode} shellMode={shellMode} />
149
149
  <nav className="sidebar-content">
150
150
  {shellMode === "devices" ? <>
151
151
  <SidebarModeTabs mode={sidebarMode} setMode={(mode) => setSidebarMode(mode as SidebarMode)} />
@@ -357,17 +357,18 @@ function SidebarItem({ activeSurface, item, recordCount, setActiveSurface, showC
357
357
  );
358
358
  }
359
359
 
360
- function SidebarHeader({ setShellMode, shellMode }: {
360
+ function SidebarHeader({ setActiveSurface, setShellMode, shellMode }: {
361
+ setActiveSurface: (surface: SurfaceId) => void;
361
362
  setShellMode: (mode: ShellMode) => void;
362
363
  shellMode: ShellMode;
363
364
  }) {
364
365
  return (
365
366
  <header className="sidebar-header">
366
367
  <div className="sidebar-titlebar">
367
- <div className="brand-lockup">
368
+ <button className="brand-lockup" onClick={() => setActiveSurface(dashboardNavItem.id)} title="Return to dashboard" type="button">
368
369
  <span className="terminal-mark" aria-hidden="true">›_</span>
369
370
  <strong>{text.aidevops}</strong>
370
- </div>
371
+ </button>
371
372
  <IconSwitch
372
373
  ariaLabel="Navigation scope"
373
374
  options={[
@@ -501,14 +502,25 @@ function SidebarFooter({ accentHue, fontPreference, fontSizePreference, setAccen
501
502
  id="font-size-slider"
502
503
  max={fontSizeOptions.length - 1}
503
504
  min="0"
504
- onChange={(event) => setFontSizePreference(fontSizeOptions[Number.parseInt(event.currentTarget.value, 10)]?.value ?? "m")}
505
+ onChange={(event) => setFontSizePreference(fontSizeOptions[Number.parseInt(event.currentTarget.value, 10)]?.value ?? "xs")}
505
506
  step="1"
506
507
  type="range"
507
508
  value={fontSizeIndex}
508
509
  />
509
- <div className="range-labels" aria-hidden="true">
510
- {fontSizeOptions.map((option) => <span className={option.value === fontSizePreference ? "active" : ""} key={option.value}>{option.label}</span>)}
511
- </div>
510
+ <fieldset className="range-labels">
511
+ <legend className="sr-only">Font size shortcuts</legend>
512
+ {fontSizeOptions.map((option) => (
513
+ <button
514
+ aria-pressed={option.value === fontSizePreference}
515
+ className={option.value === fontSizePreference ? "active" : ""}
516
+ key={option.value}
517
+ onClick={() => setFontSizePreference(option.value)}
518
+ type="button"
519
+ >
520
+ {option.label}
521
+ </button>
522
+ ))}
523
+ </fieldset>
512
524
  </div>
513
525
  <div className="font-control">
514
526
  <span id="appearance-font-selector-label">{text.font}</span>
@@ -303,15 +303,15 @@ export const text = {
303
303
  } as const;
304
304
 
305
305
  export const DEFAULT_ACCENT_HUE = 123;
306
- export const DEFAULT_FONT: FontPreference = "Menlo";
307
- export const DEFAULT_FONT_SIZE: FontSizePreference = "m";
306
+ export const DEFAULT_FONT: FontPreference = "Inter";
307
+ export const DEFAULT_FONT_SIZE: FontSizePreference = "xs";
308
308
 
309
309
  export const fontOptions: FontOption[] = [
310
310
  { value: "IBM Plex Mono", label: "IBM Plex Mono", fontFamily: '"IBM Plex Mono", Menlo, Monaco, Consolas, monospace' },
311
311
  { value: "IBM Plex Sans", label: "IBM Plex Sans", fontFamily: '"IBM Plex Sans", Inter, system-ui, sans-serif' },
312
312
  { value: "IBM Plex Serif", label: "IBM Plex Serif", fontFamily: '"IBM Plex Serif", Georgia, serif' },
313
- { value: "Inter", label: "Inter", fontFamily: 'Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' },
314
- { value: "Menlo", label: "Menlo (default)", fontFamily: 'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' },
313
+ { value: "Inter", label: "Inter (default)", fontFamily: 'Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif' },
314
+ { value: "Menlo", label: "Menlo", fontFamily: 'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' },
315
315
  { value: "Playpen Sans", label: "Playpen Sans", fontFamily: '"Playpen Sans", "Comic Sans MS", Inter, system-ui, sans-serif' },
316
316
  { value: "Poppins", label: "Poppins", fontFamily: 'Poppins, Inter, system-ui, sans-serif' },
317
317
  { value: "Source Sans", label: "Source Sans", fontFamily: '"Source Sans 3", "Source Sans Pro", "Source Sans", Inter, system-ui, sans-serif' },
@@ -543,11 +543,11 @@ export function getSystemTheme(): "light" | "dark" {
543
543
  }
544
544
 
545
545
  export function fontFamilyForPreference(font: FontPreference): string {
546
- return fontOptions.find((option) => option.value === font)?.fontFamily ?? fontOptions.find((option) => option.value === DEFAULT_FONT)?.fontFamily ?? "Menlo, monospace";
546
+ return fontOptions.find((option) => option.value === font)?.fontFamily ?? fontOptions.find((option) => option.value === DEFAULT_FONT)?.fontFamily ?? "Inter, system-ui, sans-serif";
547
547
  }
548
548
 
549
549
  export function fontSizeForPreference(size: FontSizePreference): string {
550
- return fontSizeOptions.find((option) => option.value === size)?.cssSize ?? fontSizeOptions.find((option) => option.value === DEFAULT_FONT_SIZE)?.cssSize ?? "16px";
550
+ return fontSizeOptions.find((option) => option.value === size)?.cssSize ?? fontSizeOptions.find((option) => option.value === DEFAULT_FONT_SIZE)?.cssSize ?? "14px";
551
551
  }
552
552
 
553
553
  export function isFontPreference(value: string | null): value is FontPreference {
@@ -55,7 +55,7 @@ export function renderDashboardHtml(status: GuiResponseEnvelope<GuiStatusData>):
55
55
  `<h1>aidevops app interface</h1>`,
56
56
  `<p>Made for creators.</p>`,
57
57
  `<p>AI-assisted development workflows, code quality, and deployment automation.</p>`,
58
- `<p>Theme follows system preferences with light and dark overrides. Sidebar modes: DevOps and Comms. Appearance controls can be hidden or shown, and include editable Hue, icon Reset, Show borders toggle, Show counts toggle, Font size choices xs, s, m, lg, xl, and Font options: IBM Plex Mono, IBM Plex Sans, IBM Plex Serif, Inter, Menlo (default), Playpen Sans, Poppins, Source Sans, Source Serif, Tilt Neon, Ubuntu Mono. The workspace header includes a command palette, reserved signposts/help slot immediately left of notifications, notifications menu, AI Assistant panel, and profile menu with Settings, Theme, Language, Community, Admin, and Logout entries. Notifications reuse the OpenCode toast status cache and GUI readiness data. A desktop status bar shows local readiness, repo totals, secret reference count, provider accounts, and update state.</p>`,
58
+ `<p>Theme follows system preferences with light and dark overrides. Sidebar modes: DevOps and Comms. Appearance controls can be hidden or shown, and include editable Hue, icon Reset, Show borders toggle, Show counts toggle, Font size choices xs, s, m, lg, xl, and Font options: IBM Plex Mono, IBM Plex Sans, IBM Plex Serif, Inter (default), Menlo, Playpen Sans, Poppins, Source Sans, Source Serif, Tilt Neon, Ubuntu Mono. The workspace header includes a command palette, reserved signposts/help slot immediately left of notifications, notifications menu, AI Assistant panel, and profile menu with Settings, Theme, Language, Community, Admin, and Logout entries. Notifications reuse the OpenCode toast status cache and GUI readiness data. A desktop status bar shows local readiness, repo totals, secret reference count, provider accounts, and update state.</p>`,
59
59
  `<p>Secret references stay read-only and redacted.</p>`,
60
60
  `<p>Vault surfaces show padlock indicators. Encrypted by aidevops Vault; contents visible only when unlocked through app or authorised vault commands.</p>`,
61
61
  `<p>${escapeHtml(status.data.update.message)}</p>`,
@@ -0,0 +1,49 @@
1
+ import { type Dispatch, type SetStateAction, useEffect } from "react";
2
+ import type { SurfaceId } from "./app-model";
3
+
4
+ export interface NavigationHistory {
5
+ entries: SurfaceId[];
6
+ index: number;
7
+ }
8
+
9
+ export function nextHistoryIndex(current: NavigationHistory, direction: -1 | 1): number {
10
+ return Math.min(Math.max(0, current.entries.length - 1), Math.max(0, current.index + direction));
11
+ }
12
+
13
+ export function useNavigationHistoryKeyboard(setNavigation: Dispatch<SetStateAction<NavigationHistory>>): void {
14
+ useEffect(() => {
15
+ const navigateHistoryWithKeyboard = (event: globalThis.KeyboardEvent) => {
16
+ if (shouldIgnoreHistoryShortcut(event)) {
17
+ return;
18
+ }
19
+
20
+ const direction = historyShortcutDirection(event.key);
21
+ if (direction === null) {
22
+ return;
23
+ }
24
+
25
+ event.preventDefault();
26
+ setNavigation((current) => ({ ...current, index: nextHistoryIndex(current, direction) }));
27
+ };
28
+
29
+ window.addEventListener("keydown", navigateHistoryWithKeyboard);
30
+
31
+ return () => window.removeEventListener("keydown", navigateHistoryWithKeyboard);
32
+ }, [setNavigation]);
33
+ }
34
+
35
+ function shouldIgnoreHistoryShortcut(event: globalThis.KeyboardEvent): boolean {
36
+ return !(event.metaKey || event.ctrlKey) || isEditableKeyboardTarget(event.target) || isEditableKeyboardTarget(document.activeElement);
37
+ }
38
+
39
+ function historyShortcutDirection(key: string): -1 | 1 | null {
40
+ return key === "[" ? -1 : key === "]" ? 1 : null;
41
+ }
42
+
43
+ function isEditableKeyboardTarget(target: EventTarget | null): boolean {
44
+ if (typeof HTMLElement === "undefined" || !(target instanceof HTMLElement)) {
45
+ return false;
46
+ }
47
+
48
+ return target.isContentEditable || target.closest("input, textarea, select, [contenteditable], [role='textbox']") !== null;
49
+ }
@@ -10,8 +10,8 @@
10
10
  --border-hover: color-mix(in srgb, rgb(0 0 0 / 24%) 64%, rgb(255 255 255 / 48%));
11
11
  --code-bg: hsl(var(--accent-hue) 55% 91%);
12
12
  --desktop-titlebar-height: 0px;
13
- --font-family-app: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
14
- --font-size-app: 16px;
13
+ --font-family-app: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
14
+ --font-size-app: 14px;
15
15
  --glass-panel-shadow: inset 0 1px 0 rgb(255 255 255 / 58%), inset 0 -1px 0 rgb(0 0 0 / 3%), var(--shadow-soft);
16
16
  --notification-error-bg: rgb(207 34 46 / 10%);
17
17
  --notification-error-fg: #cf222e;
@@ -119,7 +119,7 @@ code {
119
119
  .app-shell {
120
120
  display: grid;
121
121
  column-gap: 8px;
122
- grid-template-columns: 72px var(--sidebar-width, 302px) 8px minmax(0, 1fr);
122
+ grid-template-columns: 72px var(--sidebar-width, 302px) 6px minmax(0, 1fr);
123
123
  grid-template-rows: minmax(0, 1fr) auto;
124
124
  height: 100vh;
125
125
  padding: calc(var(--shell-gutter) + var(--desktop-titlebar-height)) var(--shell-gutter) var(--shell-gutter);
@@ -127,7 +127,7 @@ code {
127
127
  }
128
128
 
129
129
  .app-shell.machine-rail-collapsed {
130
- grid-template-columns: var(--sidebar-width, 302px) 8px minmax(0, 1fr);
130
+ grid-template-columns: var(--sidebar-width, 302px) 6px minmax(0, 1fr);
131
131
  }
132
132
 
133
133
  .desktop-titlebar-tagline {
@@ -296,10 +296,19 @@ code {
296
296
  }
297
297
 
298
298
  .brand-lockup {
299
+ background: transparent;
300
+ border: 0;
301
+ color: var(--text-primary);
299
302
  gap: 12px;
300
303
  padding: 2px 4px;
301
304
  }
302
305
 
306
+ .brand-lockup:hover,
307
+ .brand-lockup:focus-visible {
308
+ color: var(--accent);
309
+ outline: none;
310
+ }
311
+
303
312
  .sidebar-titlebar {
304
313
  gap: 10px;
305
314
  justify-content: space-between;
@@ -819,28 +828,32 @@ code {
819
828
  }
820
829
 
821
830
  .range-labels {
831
+ border: 0;
822
832
  color: var(--text-muted);
823
833
  display: grid;
824
834
  font-size: 0.7rem;
825
835
  font-weight: 900;
826
836
  grid-template-columns: repeat(5, 1fr);
837
+ margin: 0;
838
+ padding: 0;
827
839
  text-transform: uppercase;
828
840
  }
829
841
 
830
- .range-labels span {
842
+ .range-labels button {
843
+ background: transparent;
844
+ border: 0;
845
+ border-radius: 8px;
846
+ color: inherit;
847
+ font: inherit;
848
+ padding: 3px 0;
831
849
  text-align: center;
832
850
  }
833
851
 
834
- .range-labels span:first-child {
835
- text-align: left;
836
- }
837
-
838
- .range-labels span:last-child {
839
- text-align: right;
840
- }
841
-
842
- .range-labels .active {
852
+ .range-labels button:hover,
853
+ .range-labels button:focus-visible,
854
+ .range-labels button.active {
843
855
  color: var(--accent);
856
+ outline: none;
844
857
  }
845
858
 
846
859
  .font-control select {
@@ -2206,7 +2219,7 @@ code {
2206
2219
  align-items: center;
2207
2220
  color: var(--text-secondary);
2208
2221
  display: inline-flex;
2209
- font-size: 0.95em;
2222
+ font-size: 1em;
2210
2223
  font-weight: 800;
2211
2224
  gap: 4px;
2212
2225
  max-width: 100%;
package/setup.sh CHANGED
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
12
12
  # AI Assistant Server Access Framework Setup Script
13
13
  # Helps developers set up the framework for their infrastructure
14
14
  #
15
- # Version: 3.29.18
15
+ # Version: 3.29.19
16
16
  #
17
17
  # Quick Install:
18
18
  # npm install -g aidevops && aidevops update (recommended)