@f0rbit/overview 0.1.0 → 0.2.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.
- package/bin/overview +10 -0
- package/dist/overview.js +10361 -0
- package/package.json +22 -15
- package/bunfig.toml +0 -7
- package/packages/core/__tests__/concurrency.test.ts +0 -111
- package/packages/core/__tests__/helpers.ts +0 -60
- package/packages/core/__tests__/integration/git-status.test.ts +0 -62
- package/packages/core/__tests__/integration/scanner.test.ts +0 -140
- package/packages/core/__tests__/ocn.test.ts +0 -164
- package/packages/core/package.json +0 -13
- package/packages/core/src/cache.ts +0 -31
- package/packages/core/src/concurrency.ts +0 -44
- package/packages/core/src/devpad.ts +0 -61
- package/packages/core/src/git-graph.ts +0 -54
- package/packages/core/src/git-stats.ts +0 -201
- package/packages/core/src/git-status.ts +0 -316
- package/packages/core/src/github.ts +0 -286
- package/packages/core/src/index.ts +0 -58
- package/packages/core/src/ocn.ts +0 -74
- package/packages/core/src/scanner.ts +0 -118
- package/packages/core/src/types.ts +0 -199
- package/packages/core/src/watcher.ts +0 -128
- package/packages/core/src/worktree.ts +0 -80
- package/packages/core/tsconfig.json +0 -5
- package/packages/render/bunfig.toml +0 -8
- package/packages/render/jsx-runtime.d.ts +0 -3
- package/packages/render/package.json +0 -18
- package/packages/render/src/components/__tests__/scrollbox-height.test.tsx +0 -780
- package/packages/render/src/components/__tests__/widget-container.integration.test.tsx +0 -304
- package/packages/render/src/components/git-graph.tsx +0 -127
- package/packages/render/src/components/help-overlay.tsx +0 -108
- package/packages/render/src/components/index.ts +0 -7
- package/packages/render/src/components/repo-list.tsx +0 -127
- package/packages/render/src/components/stats-panel.tsx +0 -116
- package/packages/render/src/components/status-badge.tsx +0 -70
- package/packages/render/src/components/status-bar.tsx +0 -56
- package/packages/render/src/components/widget-container.tsx +0 -286
- package/packages/render/src/components/widgets/__tests__/widget-rendering.test.tsx +0 -326
- package/packages/render/src/components/widgets/branch-list.tsx +0 -93
- package/packages/render/src/components/widgets/commit-activity.tsx +0 -112
- package/packages/render/src/components/widgets/devpad-milestones.tsx +0 -88
- package/packages/render/src/components/widgets/devpad-tasks.tsx +0 -81
- package/packages/render/src/components/widgets/file-changes.tsx +0 -78
- package/packages/render/src/components/widgets/git-status.tsx +0 -125
- package/packages/render/src/components/widgets/github-ci.tsx +0 -98
- package/packages/render/src/components/widgets/github-issues.tsx +0 -101
- package/packages/render/src/components/widgets/github-prs.tsx +0 -119
- package/packages/render/src/components/widgets/github-release.tsx +0 -73
- package/packages/render/src/components/widgets/index.ts +0 -12
- package/packages/render/src/components/widgets/recent-commits.tsx +0 -64
- package/packages/render/src/components/widgets/registry.ts +0 -23
- package/packages/render/src/components/widgets/repo-meta.tsx +0 -80
- package/packages/render/src/config/index.ts +0 -104
- package/packages/render/src/lib/__tests__/fetch-context.test.ts +0 -200
- package/packages/render/src/lib/__tests__/widget-grid.test.ts +0 -665
- package/packages/render/src/lib/actions.ts +0 -68
- package/packages/render/src/lib/fetch-context.ts +0 -102
- package/packages/render/src/lib/filter.ts +0 -94
- package/packages/render/src/lib/format.ts +0 -36
- package/packages/render/src/lib/use-devpad.ts +0 -167
- package/packages/render/src/lib/use-github.ts +0 -75
- package/packages/render/src/lib/widget-grid.ts +0 -204
- package/packages/render/src/lib/widget-state.ts +0 -96
- package/packages/render/src/overview.tsx +0 -16
- package/packages/render/src/screens/index.ts +0 -1
- package/packages/render/src/screens/main-screen.tsx +0 -410
- package/packages/render/src/theme/index.ts +0 -37
- package/packages/render/tsconfig.json +0 -9
- 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";
|