Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5cf8586
Fix broken references
OliverJAsh Mar 13, 2026
4b579ae
Use `defineConfig` as per ESLint docs
OliverJAsh Mar 3, 2026
66dc736
Format (via pnpm)
OliverJAsh Mar 3, 2026
83345ff
Switch to tsgo
OliverJAsh Mar 3, 2026
88b1079
Enable `noUncheckedIndexedAccess`
OliverJAsh Mar 3, 2026
5544c75
Enable `noImplicitReturns`
OliverJAsh Mar 9, 2026
815bc8d
Add React Compiler
OliverJAsh Mar 3, 2026
f8131da
Add React DevTools
OliverJAsh Mar 3, 2026
b85a7a7
Enable `eslint-plugin-react-hooks`
OliverJAsh Mar 3, 2026
f55a2ac
Use Oxlint
OliverJAsh Mar 3, 2026
ba782d4
Add oxlint-tsgolint to root for VS Code extension
OliverJAsh Mar 3, 2026
4245286
Fix new Oxlint errors
OliverJAsh Mar 3, 2026
a36e418
Add React Query
OliverJAsh Mar 3, 2026
2cd0151
Format
OliverJAsh Mar 4, 2026
f5c91df
Fix position of IPC handler registrations
OliverJAsh Mar 3, 2026
8b64e92
Remove redundant wrappers
OliverJAsh Mar 4, 2026
f1999a4
Remove return types
OliverJAsh Mar 4, 2026
529d114
Remove redundant async/await
OliverJAsh Mar 4, 2026
4c39b62
Sort
OliverJAsh Mar 4, 2026
8768557
Add endpoints to Electron
OliverJAsh Mar 3, 2026
f8b641c
Named params
OliverJAsh Mar 3, 2026
1e12b28
Infer
OliverJAsh Mar 9, 2026
a649cda
Assert
OliverJAsh Mar 9, 2026
4027f1b
Methods -> properties
OliverJAsh Mar 9, 2026
5d7865a
Remove redundant async/await
OliverJAsh Mar 9, 2026
9f79b38
Add endpoints to Electron
OliverJAsh Mar 10, 2026
2ddd1c7
Use subpath imports
OliverJAsh Mar 4, 2026
ea06e01
Add agents for Lite
OliverJAsh Mar 9, 2026
08c82d2
VS Code: migrate deprecated config
OliverJAsh Mar 10, 2026
541cd98
VS Code: add config for `typescriptreact` files
OliverJAsh Mar 3, 2026
6e53e59
VS Code: configure cts as ts
OliverJAsh Mar 3, 2026
a4d62b9
Update docs
OliverJAsh Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"dbaeumer.vscode-eslint",
"svelte.svelte-vscode",
"rust-lang.rust-analyzer",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"oxc.oxc-vscode"
]
}
13 changes: 10 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
{
"eslint.useFlatConfig": true,
"eslint.validate": ["typescript", "javascript", "svelte"],
"eslint.validate": ["typescript", "typescriptreact", "javascript", "svelte"],
"eslint.workingDirectories": [{ "directory": "./", "changeProcessCWD": true }],
"svelte.enable-ts-plugin": true,
"prettier.configPath": ".prettierrc.mjs",
"prettier.useEditorConfig": false,
"editor.defaultFormatter": null,
"oxc.typeAware": true,
"oxc.unusedDisableDirectives": "warn",
"files.associations": {
"*.cts": "typescript"
},
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.allTargets": true,
"rust-analyzer.check.features": "all",
Expand All @@ -19,6 +24,9 @@
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
Expand All @@ -38,6 +46,5 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"svelte.plugin.css.globals": "packages/ui/src/**/*.css",
"typescript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.importModuleSpecifier": "non-relative"
"js/ts.preferences.importModuleSpecifier": "non-relative"
}
119 changes: 119 additions & 0 deletions apps/lite/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"jsPlugins": [
// The builtin plugin isn't 1:1 at time of writing.
{ "name": "react-hooks-js", "specifier": "eslint-plugin-react-hooks" },
{ "name": "react-query-js", "specifier": "@tanstack/eslint-plugin-query" }
],
"plugins": ["eslint", "jsx-a11y", "oxc", "react", "typescript", "unicorn"],
"rules": {
"arrow-body-style": ["error", "as-needed"],
"default-param-last": "error",
"no-cond-assign": ["warn", "always"],
"curly": ["warn", "multi"],
"no-console": "warn",
"no-fallthrough": "error",
"no-param-reassign": "error",
"prefer-template": "warn",
"oxc/no-barrel-file": ["warn", { "threshold": 0 }],
"unicorn/no-document-cookie": "warn",
"unicorn/prefer-number-properties": "error",
"react/button-has-type": "error",
"react/jsx-boolean-value": "warn",
"react/jsx-fragments": "warn",
"react/jsx-no-useless-fragment": "warn",
"react/no-array-index-key": "warn",
"react/no-danger": "error",
// Temporarily disabled during the POC phase
// "react/only-export-components": "error",
"react/self-closing-comp": "warn",
"typescript/array-type": ["warn", { "default": "generic" }],
"typescript/ban-ts-comment": "warn",
"typescript/parameter-properties": "error",
"typescript/no-explicit-any": "error",
"typescript/no-inferrable-types": "warn",
"typescript/no-non-null-assertion": "warn",
"typescript/no-unnecessary-condition": "warn",
"typescript/no-unnecessary-template-expression": "warn",
"typescript/restrict-template-expressions": [
"warn",
{
"allowAny": false,
"allowNullish": false,
"allowRegExp": false
}
],
"typescript/restrict-plus-operands": [
"error",
{
"allowAny": false,
"allowBoolean": false,
"allowNullish": false,
"allowNumberAndString": false,
"allowRegExp": false
}
],
// "always" flags for lack of await outside of async functions, unlike ESLint:
// https://github.com/oxc-project/oxc/issues/18452
"typescript/return-await": ["error", "error-handling-correctness-only"],
"typescript/strict-boolean-expressions": [
"warn",
{
"allowString": false,
"allowNumber": false,
"allowNullableBoolean": true
}
],
"typescript/await-thenable": "error",
"typescript/no-misused-promises": "error",
"typescript/no-floating-promises": "error",
"typescript/no-misused-spread": "error",
"typescript/no-unsafe-argument": "error",
"typescript/no-unsafe-assignment": "error",
"typescript/no-unsafe-call": "error",
"typescript/no-unsafe-member-access": "error",
"typescript/no-unsafe-return": "error",
"typescript/unbound-method": "error",

// Default config has catch violations.
"no-unused-vars": [
"warn",
{
"caughtErrorsIgnorePattern": "^_",
// These are the defaults that are unset by diverging our config at all.
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],

// Lots of false positives on sums.
"react/jsx-key": "off",

// Enable the recommended rules in the JS plugin as per:
// https://react.dev/reference/eslint-plugin-react-hooks#recommended
"react-hooks-js/exhaustive-deps": "error",
"react-hooks-js/rules-of-hooks": "error",
"react-hooks-js/component-hook-factories": "error",
"react-hooks-js/config": "error",
"react-hooks-js/error-boundaries": "error",
"react-hooks-js/gating": "error",
"react-hooks-js/globals": "error",
"react-hooks-js/immutability": "error",
"react-hooks-js/incompatible-library": "error",
"react-hooks-js/preserve-manual-memoization": "error",
"react-hooks-js/purity": "error",
"react-hooks-js/refs": "error",
"react-hooks-js/set-state-in-effect": "error",
"react-hooks-js/set-state-in-render": "error",
"react-hooks-js/static-components": "error",
"react-hooks-js/unsupported-syntax": "error",
"react-hooks-js/use-memo": "error",

// Enable the recommended rules in the JS plugin.
"react-query-js/exhaustive-deps": "error",
"react-query-js/no-rest-destructuring": "warn",
"react-query-js/stable-query-client": "error",
"react-query-js/no-unstable-deps": "error",
"react-query-js/infinite-query-property-order": "error"
}
}
7 changes: 7 additions & 0 deletions apps/lite/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Typechecking

Typechecking is the fastest way to validate that everything is okay. Always run this **exact** command to typecheck:

```console
$ pnpm -F @gitbutler/lite check
```
4 changes: 2 additions & 2 deletions apps/lite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Electron + React (Vite) + TanStack Router scaffold.

## ESLint

No app-local ESLint config is added. This app uses the monorepo root flat config in `eslint.config.js`.
No app-local ESLint config is added. The monorepo root flat config in `eslint.config.js` ignores `apps/lite/**`, and Lite is linted via `oxlint` instead.

## IPC contract

Expand All @@ -88,7 +88,7 @@ No app-local ESLint config is added. This app uses the monorepo root flat config

`apps/lite` consumes Rust bindings via `@gitbutler/but-sdk` using a strict process boundary:

1. **Electron main process** calls native bindings (for example `listProjectsNapi`) in `electron/src/model/projects.ts`.
1. **Electron main process** defines IPC handlers in `electron/src/main.ts` that call native bindings (for example `listProjectsStatelessNapi`).
2. **IPC layer** defines request/response contracts in `electron/src/ipc.ts` and carries SDK-generated types such as `ProjectForFrontend`.
3. **Preload** forwards approved methods through `contextBridge` in `electron/src/preload.cts`.
4. **Renderer** calls `window.lite.*` and never imports or calls native bindings directly.
Expand Down
164 changes: 157 additions & 7 deletions apps/lite/electron/src/ipc.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,165 @@
import type { ProjectForFrontend, RefInfo } from "@gitbutler/but-sdk";
import type {
ApplyOutcome,
AssignmentRejection,
BranchDetails,
BranchListing,
BranchListingFilter,
HunkAssignmentRequest,
CommitDetails,
DiffSpec,
InsertSide,
ProjectForFrontend,
RelativeTo,
RefInfo,
TreeChange,
TreeChanges,
UICommitCreateResult,
UICommitInsertBlankResult,
UICommitMoveResult,
UICommitRewordResult,
UIMoveChangesResult,
UnifiedPatch,
WorktreeChanges,
} from "@gitbutler/but-sdk";

export interface AssignHunkParams {
projectId: string;
assignments: Array<HunkAssignmentRequest>;
}

export interface ApplyParams {
projectId: string;
existingBranch: string;
}

export interface BranchDetailsParams {
projectId: string;
branchName: string;
remote: string | null;
}

export interface BranchDiffParams {
projectId: string;
branch: string;
}

export interface CommitAmendParams {
projectId: string;
commitId: string;
changes: Array<DiffSpec>;
}

export interface CommitDetailsWithLineStatsParams {
projectId: string;
commitId: string;
}

export interface CommitCreateParams {
projectId: string;
relativeTo: RelativeTo;
side: InsertSide;
changes: Array<DiffSpec>;
message: string;
}

export interface CommitMoveChangesBetweenParams {
projectId: string;
sourceCommitId: string;
destinationCommitId: string;
changes: Array<DiffSpec>;
}

export interface CommitMoveParams {
projectId: string;
subjectCommitId: string;
anchorCommitId: string;
side: InsertSide;
}

export interface CommitMoveToBranchParams {
projectId: string;
subjectCommitId: string;
anchorRef: string;
}

export interface CommitInsertBlankParams {
projectId: string;
relativeTo: RelativeTo;
side: InsertSide;
}

export interface CommitRewordParams {
projectId: string;
commitId: string;
message: string;
}

export interface CommitUncommitChangesParams {
projectId: string;
commitId: string;
changes: Array<DiffSpec>;
assignTo: string | null;
}

export interface TreeChangeDiffParams {
projectId: string;
change: TreeChange;
}

export interface UnapplyStackParams {
projectId: string;
stackId: string;
}

export interface LiteElectronApi {
ping(input: string): Promise<string>;
getVersion(): Promise<string>;
listProjects(): Promise<ProjectForFrontend[]>;
headInfo(projectId: string): Promise<RefInfo>;
apply: (params: ApplyParams) => Promise<ApplyOutcome>;
assignHunk: (params: AssignHunkParams) => Promise<Array<AssignmentRejection>>;
branchDetails: (params: BranchDetailsParams) => Promise<BranchDetails>;
branch_diff: (params: BranchDiffParams) => Promise<TreeChanges>;
changesInWorktree: (projectId: string) => Promise<WorktreeChanges>;
commitAmend: (params: CommitAmendParams) => Promise<UICommitCreateResult>;
commitCreate: (params: CommitCreateParams) => Promise<UICommitCreateResult>;
commitDetailsWithLineStats: (params: CommitDetailsWithLineStatsParams) => Promise<CommitDetails>;
commitInsertBlank: (params: CommitInsertBlankParams) => Promise<UICommitInsertBlankResult>;
commitMove: (params: CommitMoveParams) => Promise<UICommitMoveResult>;
commitMoveToBranch: (params: CommitMoveToBranchParams) => Promise<UICommitMoveResult>;
commitReword: (params: CommitRewordParams) => Promise<UICommitRewordResult>;
commitMoveChangesBetween: (
params: CommitMoveChangesBetweenParams,
) => Promise<UIMoveChangesResult>;
commitUncommitChanges: (params: CommitUncommitChangesParams) => Promise<UIMoveChangesResult>;
getVersion: () => Promise<string>;
headInfo: (projectId: string) => Promise<RefInfo>;
listBranches: (
projectId: string,
filter: BranchListingFilter | null,
) => Promise<Array<BranchListing>>;
listProjects: () => Promise<Array<ProjectForFrontend>>;
ping: (input: string) => Promise<string>;
treeChangeDiffs: (params: TreeChangeDiffParams) => Promise<UnifiedPatch | null>;
unapplyStack: (params: UnapplyStackParams) => Promise<void>;
}

export const liteIpcChannels = {
ping: "lite:ping",
apply: "workspace:apply",
assignHunk: "workspace:assign-hunk",
branchDetails: "workspace:branch-details",
branchDiff: "workspace:branch-diff",
changesInWorktree: "workspace:changes-in-worktree",
commitAmend: "workspace:commit-amend",
commitCreate: "workspace:commit-create",
commitDetailsWithLineStats: "workspace:commit-details-with-line-stats",
commitInsertBlank: "workspace:commit-insert-blank",
commitMove: "workspace:commit-move",
commitMoveToBranch: "workspace:commit-move-to-branch",
commitReword: "workspace:commit-reword",
commitMoveChangesBetween: "workspace:commit-move-changes-between",
commitUncommitChanges: "workspace:commit-uncommit-changes",
getVersion: "lite:get-version",
listProjects: "projects:list",
headInfo: "workspace:head-info",
listBranches: "workspace:list-branches",
listProjects: "projects:list",
ping: "lite:ping",
treeChangeDiffs: "workspace:tree-change-diffs",
unapplyStack: "workspace:unapply-stack",
} as const;
Loading
Loading