Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function App
created: 2026-10-15T03:38:04Z
norm: '1'
sig: 063d0f00fea02511
body_hash: 83384f8adacc5fe0
raw_hash: 8ef778bdb202cc6b
vouched: 2026-10-22T21:26:46Z
body_hash: b43eff10846d0cd3
raw_hash: c90b02e06b07e972
vouched: 2026-10-22T22:08:11Z
lines: 235-824
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function App
created: 2026-10-22T20:29:16Z
norm: '1'
sig: 063d0f00fea02511
body_hash: 83384f8adacc5fe0
raw_hash: 8ef778bdb202cc6b
vouched: 2026-10-22T21:26:46Z
body_hash: b43eff10846d0cd3
raw_hash: c90b02e06b07e972
vouched: 2026-10-22T22:08:11Z
lines: 330-1237
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function Dashboard
created: 2026-10-19T20:51:03Z
norm: '1'
sig: 4bfb45497157c0e0
body_hash: 7bc89e0d12e7b984
raw_hash: cc8a4b9d6c396b11
vouched: 2026-10-22T20:44:27Z
body_hash: e77359bc3330d503
raw_hash: 7884a688975d2f4c
vouched: 2026-10-22T22:08:11Z
lines: 256-606
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ id: 01M5JNQ72H3WS44JG82D60Q37M
anchor: function InitGit
created: 2026-10-22T20:44:24Z
norm: '1'
sig: e05f5c81094473f3
body_hash: faffdcfc736d2780
raw_hash: e2bf3f4586e0df5c
sig: bbe6f480fcc6cbd1
body_hash: 0fc448d2189f968d
raw_hash: 517902d11c496496
vouched: 2026-10-22T22:08:11Z
lines: 52-111
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: function projects
created: 2026-10-15T02:51:27Z
norm: '1'
sig: a2ec110fa31d81ee
body_hash: b6503a52b484f8c1
raw_hash: 512eae2c23a06106
vouched: 2026-10-22T14:33:26Z
body_hash: 61e3738b3ac3c6ad
raw_hash: edf6c307fa9849bb
vouched: 2026-10-22T22:08:11Z
lines: 42-91
---

Expand Down
13 changes: 8 additions & 5 deletions apps/app/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -689,14 +689,16 @@ export function App() {
}
}

async function init(path: string) {
async function init(path: string, empty?: boolean) {
try {
await host.request<Project>({ op: "project-init", path });
toast.success("Initialized Git", {
toast.success(empty ? "Made the first commit" : "Initialized Git", {
description: "Committed the folder's files as Initial commit. Channels can start here now.",
});
} catch (error) {
toast.error("Could not initialize Git", { description: (error as Error).message });
toast.error(empty ? "Could not make the first commit" : "Could not initialize Git", {
description: (error as Error).message,
});
}
}

Expand Down Expand Up @@ -1068,7 +1070,7 @@ export function App() {
local={local}
connected={connected}
onSetup={!local && current.repo ? () => startSetup(current) : undefined}
onInit={() => init(current.path)}
onInit={() => init(current.path, current.empty)}
onProject={chooseProject}
onRemove={removeRepo}
onOpen={() => void choose()}
Expand Down Expand Up @@ -1128,10 +1130,11 @@ export function App() {
<div className="w-full max-w-[34rem]">
<InitGit
name={current.label}
empty={current.empty}
ignored={current.ignored}
disabled={!connected}
path={current.path}
onInit={() => init(current.path)}
onInit={() => init(current.path, current.empty)}
/>
</div>
)}
Expand Down
1 change: 1 addition & 0 deletions apps/app/src/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ export function Dashboard(
<InitGit
path={project.path}
name={project.label}
empty={project.empty}
ignored={project.ignored}
disabled={!connected}
onInit={onInit}
Expand Down
37 changes: 27 additions & 10 deletions apps/app/src/open-project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ export function EmptyProjects({ onOpen, onClone, disabled }: {
}

/** Lanes are Git worktrees, so a folder needs a commit before it can start channels. */
export function InitGit({ path, name, ignored, disabled, onInit }: {
export function InitGit({ path, name, empty, ignored, disabled, onInit }: {
path: string;
name: string;
/** The folder is already a Git repository and needs only its first commit. */
empty?: boolean;
/** Ace won't commit under a folder's own `.gitignore`, so the person commits it themselves. */
ignored?: boolean;
disabled: boolean;
Expand All @@ -77,32 +79,47 @@ export function InitGit({ path, name, ignored, disabled, onInit }: {
}
}

const lanes = empty
? `Channels work in lanes, which are Git worktrees, and ${name} has no commit yet.`
: "Channels work in lanes, which are Git worktrees.";
return (
<div className="flex w-full flex-col items-center gap-3 rounded-lg border border-border/70 bg-muted/30 px-5 py-6 text-center">
<IconBranch className="size-5 text-muted-foreground" aria-hidden />
<h2 className="text-sm font-medium">Initialize a Git repository to get started</h2>
<h2 className="text-sm font-medium">
{empty
? "Make the first commit to get started"
: "Initialize a Git repository to get started"}
</h2>
{ignored
? (
<p className="max-w-[30rem] text-pretty text-xs leading-5 text-muted-foreground">
Channels work in lanes, which are Git worktrees. {name} already has a{" "}
<code>.gitignore</code>, so Ace won't commit under its rules. Run <code>git init</code>
{" "}
and commit its files yourself, then channels can start here.
{lanes} {name} already has a{" "}
<code>.gitignore</code>, so Ace won't commit under its rules. {empty ? "Commit" : (
<>
Run <code>git init</code> and commit
</>
)} its files yourself, then channels can start here.
</p>
)
: (
<>
<p className="max-w-[30rem] text-pretty text-xs leading-5 text-muted-foreground">
Channels work in lanes, which are Git worktrees. Ace will run <code>git init</code> in
{" "}
{name}, add a <code>.gitignore</code> that leaves out secrets such as{" "}
{lanes} Ace will {empty ? "add" : (
<>
run <code>git init</code> in {name}, add
</>
)} a <code>.gitignore</code> that leaves out secrets such as{" "}
<code>.env</code>, dependencies, and build output, and commit the rest.
</p>
<Button
disabled={disabled || busy}
onClick={() => void init()}
>
{busy ? "Initializing…" : "Initialize Git"}
{busy
? empty ? "Committing…" : "Initializing…"
: empty
? "Make first commit"
: "Initialize Git"}
</Button>
</>
)}
Expand Down
1 change: 1 addition & 0 deletions apps/app/src/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export function projects(opened: Project[], channels: Listing[], host: string):
};
if (repo) created.repo = repo;
if (opened?.git === false) created.git = false;
if (opened?.empty) created.empty = true;
if (opened?.ignored) created.ignored = true;
return void values.set(id, created);
}
Expand Down
1 change: 1 addition & 0 deletions apps/host/src/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ async function project(path: string): Promise<Project> {
if (repo) value.repo = repo;
if (!commit) {
value.git = false;
if (existsSync(join(root, ".git"))) value.empty = true;
if (existsSync(join(root, ".gitignore"))) value.ignored = true;
}
return value;
Expand Down
2 changes: 2 additions & 0 deletions apps/host/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ export type Project = {
repo?: string;
/** False until the folder is a Git repository with a commit, which lanes start from. */
git?: false;
/** The folder is already a Git repository and needs only its first commit. */
empty?: true;
/** The folder without a commit already has a `.gitignore`, so Ace leaves initializing it to its owner. */
ignored?: true;
};
Expand Down