@f0rbit/overview 0.2.0 → 0.2.2

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.
Files changed (69) hide show
  1. package/bin/overview +1 -1
  2. package/dist/overview.js +11898 -0
  3. package/package.json +10 -13
  4. package/bunfig.toml +0 -7
  5. package/packages/core/__tests__/concurrency.test.ts +0 -111
  6. package/packages/core/__tests__/helpers.ts +0 -60
  7. package/packages/core/__tests__/integration/git-status.test.ts +0 -62
  8. package/packages/core/__tests__/integration/scanner.test.ts +0 -140
  9. package/packages/core/__tests__/ocn.test.ts +0 -164
  10. package/packages/core/package.json +0 -13
  11. package/packages/core/src/cache.ts +0 -31
  12. package/packages/core/src/concurrency.ts +0 -44
  13. package/packages/core/src/devpad.ts +0 -61
  14. package/packages/core/src/git-graph.ts +0 -54
  15. package/packages/core/src/git-stats.ts +0 -201
  16. package/packages/core/src/git-status.ts +0 -316
  17. package/packages/core/src/github.ts +0 -286
  18. package/packages/core/src/index.ts +0 -58
  19. package/packages/core/src/ocn.ts +0 -74
  20. package/packages/core/src/scanner.ts +0 -118
  21. package/packages/core/src/types.ts +0 -199
  22. package/packages/core/src/watcher.ts +0 -128
  23. package/packages/core/src/worktree.ts +0 -80
  24. package/packages/core/tsconfig.json +0 -5
  25. package/packages/render/bunfig.toml +0 -8
  26. package/packages/render/jsx-runtime.d.ts +0 -3
  27. package/packages/render/package.json +0 -18
  28. package/packages/render/src/components/__tests__/scrollbox-height.test.tsx +0 -780
  29. package/packages/render/src/components/__tests__/widget-container.integration.test.tsx +0 -306
  30. package/packages/render/src/components/git-graph.tsx +0 -127
  31. package/packages/render/src/components/help-overlay.tsx +0 -108
  32. package/packages/render/src/components/index.ts +0 -7
  33. package/packages/render/src/components/repo-list.tsx +0 -127
  34. package/packages/render/src/components/stats-panel.tsx +0 -116
  35. package/packages/render/src/components/status-badge.tsx +0 -70
  36. package/packages/render/src/components/status-bar.tsx +0 -56
  37. package/packages/render/src/components/widget-container.tsx +0 -286
  38. package/packages/render/src/components/widgets/__tests__/widget-rendering.test.tsx +0 -326
  39. package/packages/render/src/components/widgets/branch-list.tsx +0 -93
  40. package/packages/render/src/components/widgets/commit-activity.tsx +0 -112
  41. package/packages/render/src/components/widgets/devpad-milestones.tsx +0 -88
  42. package/packages/render/src/components/widgets/devpad-tasks.tsx +0 -81
  43. package/packages/render/src/components/widgets/file-changes.tsx +0 -78
  44. package/packages/render/src/components/widgets/git-status.tsx +0 -125
  45. package/packages/render/src/components/widgets/github-ci.tsx +0 -98
  46. package/packages/render/src/components/widgets/github-issues.tsx +0 -101
  47. package/packages/render/src/components/widgets/github-prs.tsx +0 -119
  48. package/packages/render/src/components/widgets/github-release.tsx +0 -73
  49. package/packages/render/src/components/widgets/index.ts +0 -12
  50. package/packages/render/src/components/widgets/recent-commits.tsx +0 -64
  51. package/packages/render/src/components/widgets/registry.ts +0 -23
  52. package/packages/render/src/components/widgets/repo-meta.tsx +0 -80
  53. package/packages/render/src/config/index.ts +0 -104
  54. package/packages/render/src/lib/__tests__/fetch-context.test.ts +0 -200
  55. package/packages/render/src/lib/__tests__/widget-grid.test.ts +0 -665
  56. package/packages/render/src/lib/actions.ts +0 -68
  57. package/packages/render/src/lib/fetch-context.ts +0 -102
  58. package/packages/render/src/lib/filter.ts +0 -94
  59. package/packages/render/src/lib/format.ts +0 -36
  60. package/packages/render/src/lib/use-devpad.ts +0 -167
  61. package/packages/render/src/lib/use-github.ts +0 -75
  62. package/packages/render/src/lib/widget-grid.ts +0 -204
  63. package/packages/render/src/lib/widget-state.ts +0 -96
  64. package/packages/render/src/overview.tsx +0 -16
  65. package/packages/render/src/screens/index.ts +0 -1
  66. package/packages/render/src/screens/main-screen.tsx +0 -410
  67. package/packages/render/src/theme/index.ts +0 -37
  68. package/packages/render/tsconfig.json +0 -9
  69. package/tsconfig.json +0 -23
@@ -1,204 +0,0 @@
1
- import { BorderChars, type BorderSides } from "@opentui/core";
2
- import type { WidgetId, WidgetSpan, WidgetSizeHint, WidgetConfig } from "@overview/core";
3
-
4
- const B = BorderChars.rounded;
5
-
6
- // ── Types ──
7
-
8
- export interface GridWidget {
9
- id: WidgetId;
10
- size_hint: WidgetSizeHint;
11
- config: WidgetConfig;
12
- }
13
-
14
- export interface GridRow {
15
- widgets: GridWidget[];
16
- columns: 1 | 2 | 3;
17
- }
18
-
19
- export interface GridLayout {
20
- rows: GridRow[];
21
- total_width: number;
22
- }
23
-
24
- // ── Span resolution ──
25
-
26
- export function resolveSpan(span: WidgetSpan, panel_width: number): "full" | "half" | "third" {
27
- if (span === "third") {
28
- if (panel_width >= 60) return "third";
29
- if (panel_width >= 40) return "half";
30
- return "full";
31
- }
32
- if (span === "half" && panel_width >= 40) return "half";
33
- return "full";
34
- }
35
-
36
- // ── Row computation ──
37
-
38
- export function computeRows(widgets: GridWidget[], panel_width: number): GridRow[] {
39
- const enabled = widgets.filter((w) => w.config.enabled);
40
-
41
- const fulls: GridWidget[] = [];
42
- const halfs: GridWidget[] = [];
43
- const thirds: GridWidget[] = [];
44
-
45
- for (const widget of enabled) {
46
- const effective = resolveSpan(widget.size_hint.span, panel_width);
47
- if (effective === "full") {
48
- fulls.push(widget);
49
- } else if (effective === "half") {
50
- halfs.push(widget);
51
- } else {
52
- thirds.push(widget);
53
- }
54
- }
55
-
56
- const rows: GridRow[] = [];
57
-
58
- for (const widget of fulls) {
59
- rows.push({ widgets: [widget], columns: 1 });
60
- }
61
-
62
- // Group thirds into rows of 3; leftovers auto-expand
63
- for (let i = 0; i < thirds.length; i += 3) {
64
- if (i + 2 < thirds.length) {
65
- rows.push({ widgets: [thirds[i]!, thirds[i + 1]!, thirds[i + 2]!], columns: 3 });
66
- } else if (i + 1 < thirds.length) {
67
- // 2 leftover thirds → auto-expand to 2-column row
68
- rows.push({ widgets: [thirds[i]!, thirds[i + 1]!], columns: 2 });
69
- } else {
70
- // 1 leftover third → auto-expand to 1-column row
71
- rows.push({ widgets: [thirds[i]!], columns: 1 });
72
- }
73
- }
74
-
75
- // Group halfs into rows of 2; leftover auto-expands
76
- for (let i = 0; i < halfs.length; i += 2) {
77
- if (i + 1 < halfs.length) {
78
- rows.push({ widgets: [halfs[i]!, halfs[i + 1]!], columns: 2 });
79
- } else {
80
- rows.push({ widgets: [halfs[i]!], columns: 1 });
81
- }
82
- }
83
-
84
- rows.sort((a, b) => {
85
- const min_a = Math.min(...a.widgets.map((w) => w.config.priority));
86
- const min_b = Math.min(...b.widgets.map((w) => w.config.priority));
87
- return min_a - min_b;
88
- });
89
-
90
- return rows;
91
- }
92
-
93
- // ── Full grid layout computation ──
94
-
95
- export function computeGridLayout(widgets: GridWidget[], panel_width: number): GridLayout {
96
- return {
97
- rows: computeRows(widgets, panel_width),
98
- total_width: panel_width,
99
- };
100
- }
101
-
102
- // ── Horizontal border line generation ──
103
-
104
- function cornerChar(type: "top" | "mid" | "bottom", side: "left" | "right"): string {
105
- if (type === "top") return side === "left" ? B.topLeft : B.topRight;
106
- if (type === "bottom") return side === "left" ? B.bottomLeft : B.bottomRight;
107
- return side === "left" ? B.leftT : B.rightT;
108
- }
109
-
110
- function junctionColumns(row: GridRow | null, total_width: number): Set<number> {
111
- if (!row) return new Set();
112
- if (row.columns === 2) return new Set([Math.floor(total_width / 2)]);
113
- if (row.columns === 3) return new Set([Math.floor(total_width / 3), Math.floor(2 * total_width / 3)]);
114
- return new Set();
115
- }
116
-
117
- function junctionChar(
118
- type: "top" | "mid" | "bottom",
119
- in_prev: boolean,
120
- in_next: boolean,
121
- ): string {
122
- if (type === "top") return B.topT; // junction only from below
123
- if (type === "bottom") return B.bottomT; // junction only from above
124
-
125
- // mid: check both directions
126
- if (in_prev && in_next) return B.cross;
127
- if (in_prev) return B.bottomT;
128
- if (in_next) return B.topT;
129
- return B.horizontal;
130
- }
131
-
132
- export function buildBorderLine(
133
- type: "top" | "mid" | "bottom",
134
- total_width: number,
135
- prev_row: GridRow | null,
136
- next_row: GridRow | null,
137
- ): string {
138
- if (total_width <= 0) return "";
139
-
140
- const prev_junctions = junctionColumns(type === "top" ? null : prev_row, total_width);
141
- const next_junctions = junctionColumns(type === "bottom" ? null : next_row, total_width);
142
- const all_junctions = new Set([...prev_junctions, ...next_junctions]);
143
-
144
- const chars: string[] = [];
145
- for (let col = 0; col < total_width; col++) {
146
- if (col === 0) {
147
- chars.push(cornerChar(type, "left"));
148
- } else if (col === total_width - 1) {
149
- chars.push(cornerChar(type, "right"));
150
- } else if (all_junctions.has(col)) {
151
- chars.push(junctionChar(type, prev_junctions.has(col), next_junctions.has(col)));
152
- } else {
153
- chars.push(B.horizontal);
154
- }
155
- }
156
-
157
- return chars.join("");
158
- }
159
-
160
- // ── Title insertion into border line ──
161
-
162
- export function buildBorderLineWithTitle(line: string, title: string): string {
163
- if (title.length === 0 || line.length < 4) return line;
164
- const title_str = ` ${title} `;
165
- if (title_str.length >= line.length - 2) {
166
- // Title too long — truncate
167
- const available = line.length - 4; // leave corners + 1 char each side
168
- if (available <= 0) return line;
169
- const truncated = ` ${title.slice(0, available - 1)}… `;
170
- return line.slice(0, 1) + truncated + line.slice(1 + truncated.length);
171
- }
172
- return line.slice(0, 1) + title_str + line.slice(1 + title_str.length);
173
- }
174
-
175
- // ── Widget border sides ──
176
-
177
- export function getWidgetBorderSides(row: GridRow, widget_index: number): BorderSides[] {
178
- if (row.columns === 1) {
179
- return ["left", "right"];
180
- }
181
- // Multi-column row: last widget gets both sides, others get left only
182
- if (widget_index === row.columns - 1) {
183
- return ["left", "right"];
184
- }
185
- return ["left"];
186
- }
187
-
188
- // ── Content width calculation ──
189
-
190
- export function contentWidth(span: WidgetSpan, panel_width: number): number {
191
- const resolved = resolveSpan(span, panel_width);
192
- if (resolved === "full") {
193
- // border={["left", "right"]} takes 2 chars
194
- return Math.max(1, panel_width - 2);
195
- }
196
- if (resolved === "third") {
197
- // First column width minus left border (1 char)
198
- const first_junction = Math.floor(panel_width / 3);
199
- return Math.max(1, first_junction - 1);
200
- }
201
- // Half: right column width minus 2 border chars
202
- const junction = Math.floor(panel_width / 2);
203
- return Math.max(1, panel_width - junction - 2);
204
- }
@@ -1,96 +0,0 @@
1
- import { ok, err, try_catch, type Result } from "@f0rbit/corpus";
2
- import { createSignal } from "solid-js";
3
- import type { WidgetConfig } from "@overview/core";
4
- import { join } from "path";
5
- import { homedir } from "os";
6
- import { mkdir } from "fs/promises";
7
-
8
- export interface WidgetStateFile {
9
- widgets: WidgetConfig[];
10
- devpad?: {
11
- api_url: string;
12
- api_key: string;
13
- };
14
- }
15
-
16
- const STATE_PATH = join(homedir(), ".config", "overview", "widgets.json");
17
-
18
- const [_widgetState, _setWidgetState] = createSignal<WidgetStateFile>({ widgets: [] });
19
-
20
- export function getWidgetState(): WidgetStateFile {
21
- return _widgetState();
22
- }
23
-
24
- export function updateWidgetState(state: WidgetStateFile): void {
25
- _setWidgetState(state);
26
- }
27
-
28
- export function defaultWidgetConfig(): WidgetConfig[] {
29
- return [
30
- { id: "git-status", enabled: true, priority: 0, collapsed: false },
31
- { id: "recent-commits", enabled: true, priority: 1, collapsed: false },
32
- { id: "github-prs", enabled: true, priority: 2, collapsed: false },
33
- { id: "devpad-tasks", enabled: true, priority: 3, collapsed: false },
34
- { id: "devpad-milestones", enabled: false, priority: 4, collapsed: false },
35
- { id: "file-changes", enabled: true, priority: 5, collapsed: false },
36
- { id: "repo-meta", enabled: true, priority: 6, collapsed: false },
37
- { id: "github-ci", enabled: true, priority: 7, collapsed: false },
38
- { id: "branch-list", enabled: true, priority: 8, collapsed: false },
39
- { id: "commit-activity", enabled: false, priority: 9, collapsed: false },
40
- { id: "github-issues", enabled: false, priority: 10, collapsed: false },
41
- { id: "github-release", enabled: false, priority: 11, collapsed: false },
42
- ];
43
- }
44
-
45
- export function defaultWidgetState(): WidgetStateFile {
46
- return { widgets: defaultWidgetConfig() };
47
- }
48
-
49
- function safeParse(text: string): Result<unknown, string> {
50
- return try_catch(
51
- () => JSON.parse(text),
52
- () => "invalid JSON in widget state file",
53
- );
54
- }
55
-
56
- function mergeConfigs(user_widgets: WidgetConfig[]): WidgetConfig[] {
57
- const defaults = defaultWidgetConfig();
58
- const user_ids = new Set(user_widgets.map((w) => w.id));
59
- const missing = defaults.filter((d) => !user_ids.has(d.id));
60
- return [...user_widgets, ...missing];
61
- }
62
-
63
- export async function loadWidgetState(): Promise<Result<WidgetStateFile, string>> {
64
- const file = Bun.file(STATE_PATH);
65
- const exists = await file.exists();
66
-
67
- if (!exists) return ok(defaultWidgetState());
68
-
69
- const text = await file.text().catch(() => null);
70
- if (text === null) return err("failed to read widget state file");
71
-
72
- let parsed: Partial<WidgetStateFile>;
73
- const parse_result = safeParse(text);
74
- if (!parse_result.ok) return err(parse_result.error);
75
- parsed = parse_result.value as Partial<WidgetStateFile>;
76
-
77
- const widgets = Array.isArray(parsed.widgets)
78
- ? mergeConfigs(parsed.widgets)
79
- : defaultWidgetConfig();
80
-
81
- return ok({
82
- widgets,
83
- devpad: parsed.devpad,
84
- });
85
- }
86
-
87
- export async function saveWidgetState(state: WidgetStateFile): Promise<Result<void, string>> {
88
- const dir = join(homedir(), ".config", "overview");
89
- await mkdir(dir, { recursive: true }).catch(() => {});
90
-
91
- const text = JSON.stringify(state, null, "\t");
92
- const written = await Bun.write(STATE_PATH, text).catch(() => null);
93
- if (written === null) return err("failed to write widget state file");
94
-
95
- return ok(undefined);
96
- }
@@ -1,16 +0,0 @@
1
- import { render } from "@opentui/solid";
2
- import { loadConfig, parseCliArgs, mergeCliArgs } from "./config";
3
- import { MainScreen } from "./screens";
4
-
5
- const configResult = await loadConfig();
6
- if (!configResult.ok) {
7
- console.error("Failed to load config:", configResult.error);
8
- process.exit(1);
9
- }
10
-
11
- const cliArgs = parseCliArgs(Bun.argv);
12
- const config = mergeCliArgs(configResult.value, cliArgs);
13
-
14
- const App = () => <MainScreen config={config} />;
15
-
16
- render(App);
@@ -1 +0,0 @@
1
- export { MainScreen } from "./main-screen";