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
2 changes: 1 addition & 1 deletion docs/architecture/cli-output-in-terminal.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CLI output is echoed into xterm, not run inside the shell

GitMastery commands started from the app (`setup`, `download`, `verify`) still run as a
GitMastery commands started from the app (`download`, `verify`) still run as a
separate `child_process.spawn`. Their stdout and stderr are painted into the xterm pane so
`INFO` lines stay in scrollback instead of a toast.

Expand Down
28 changes: 28 additions & 0 deletions docs/architecture/cli-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# CLI resolution is the learner's PATH

Spawned Git-Mastery commands (`download`, `verify`) and the in-app terminal resolve
`gitmastery` the same way: they inherit `getCliEnvironment()`. That is the PATH the
app was launched with, plus Homebrew prefixes on macOS. The app does not install
the CLI and does not add any other directory.

## Why the app does not install the CLI

Windows and Linux used to download a binary into the learner's save folder and
prepend that folder to PATH. That made a self-install invisible to Verify, and it
put a learner-chosen folder (often Downloads or Documents) ahead of system tools,
so a stray executable there could shadow `git`. macOS installed through Homebrew
instead, so the app had two install models that failed in different ways.

The lessons already tell learners to install the CLI and run `gitmastery setup`.
The app follows that and only checks that the result is visible.

## Known limits

- `process.env` is a snapshot from app launch. On Windows, a CLI added to PATH
while the app is running stays invisible until restart. macOS and Linux installs
land in directories already on the launch PATH (Homebrew prefixes are added by
the app; apt installs into `/usr/bin`), so they show up without a restart.
- A binary an older build downloaded into the save folder is no longer on PATH.
Those learners install the CLI the way the lessons describe.
- Windows resolution looks for `gitmastery.exe` only. Shim-based installs
(`.cmd` / `.bat`) are unsupported; running those needs `shell: true`.
5 changes: 2 additions & 3 deletions docs/architecture/exercise-directory-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,7 @@ is already in the terminal. Failures, including a failed download, settle the sa
re-downloads. Blocked on the flag shipping — it does not exist in v7.8.2. When picked up it
needs:

- a minimum CLI version gate (`get-gitmastery-version` in `src/electron/ipc/setupPrereq.ts`
already reads the version)
- a minimum CLI version gate (the app no longer reads the CLI version; that would need a new probe)
- a destructive-action confirmation, since it deletes the learner's work
- a fallback for older CLIs: hide the button rather than shell out to a flag that errors

Expand Down Expand Up @@ -287,7 +286,7 @@ folder, so a stray `cd` into a subdirectory is sent back through Start.

## 10. Process spawn failures

`_setup`, `_download` and `_verify` each listen for `'error'` on the child process. A spawn that
`_download` and `_verify` each listen for `'error'` on the child process. A spawn that
never starts — GitMastery missing from `PATH`, exercise folder gone — emits `'error'` and then
usually `'close'` with `code === null`. Without an `error` listener Node raises this as an
uncaught exception in the main process. `_download` must still settle its promise on both
Expand Down
23 changes: 0 additions & 23 deletions docs/architecture/exercise-folder-location.md

This file was deleted.

65 changes: 65 additions & 0 deletions docs/architecture/exercises-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# The exercises root is one the learner already created

The app stores `exercisesRoot`: the folder `gitmastery setup` created, which is the
folder containing `.gitmastery.json`. It is not the parent, and the app never
creates it.

## Why the app does not run setup

Learners who follow the lessons in order are told not to set Git-Mastery up in
advance. The lessons introduce the CLI and `gitmastery setup` when they are
needed. Running setup from an onboarding screen asked for that work before the
lesson did, and it required the CLI to be installed first.

The first time a learner clicks Start, the app walks through three steps. The
introduction is shown once. The other two are checked on every Start, from the
main process, so the Start button in the app and the one in the lesson page
behave the same.

1. **Introduction.** A short description of an exercise: a folder of starting
files, work in the terminal, then Verify. Hands-on practicals are mentioned
because they have no Verify step.
2. **Tools.** `gitmastery` must be on PATH. On Windows, Git Bash must be
installed too, because the in-app terminal uses it. Neither check runs the
CLI. On Windows the step says to restart the app, because PATH is read at
launch and a retry cannot see a change. On macOS and Linux the step can be
checked again immediately.
3. **Folder.** The learner picks the exercises folder. It is accepted when it
contains `.gitmastery.json`. If they pick the parent and exactly one
subfolder is a root, that subfolder is used. The copy says this step does
not run setup. "Setup Instructions" in that sentence opens T1L2
(`/lessons/gitPrep/#installing-the-git-mastery-app`) in the embedded site
and closes the dialog, because the native view is hidden while a dialog is
open. An X beside the path forgets the link. It does not delete the folder.

Git, `user.name` and `user.email` are not checked separately. `gitmastery setup`
refuses to finish unless they are in place, and only a finished setup writes
`.gitmastery.json`. A valid root already proves those checks passed. Running
`gitmastery check git` would add several seconds and is skipped. GitHub CLI
requirements are not listed per exercise; `gitmastery download` reports them.

Closing the steps does not show an error. The next Start resumes at whichever
step is still missing. Once every step passes, the exercise starts on its own.

## Why there is no lock

The app owns no files in the root, so pointing Settings at a different one is a
setting change. Progress is read from disk again. Older builds stored the parent
folder and assumed the child was named `gitmastery-exercises`, which broke
learners who accepted setup's offer to rename it. Storing the root itself allows
any name. An existing parent-folder setting is migrated on launch when
`gitmastery-exercises` exists underneath it.

The folder step includes a fixed warning against OneDrive, Dropbox, Google Drive
and iCloud. The path is not inspected: iCloud sync of Desktop and Documents does
not appear in the path, so a check would miss the case the warning exists for.

## Rejected alternatives

- **The app runs `gitmastery setup`.** Needs the CLI first, and repeats a step
the lesson already asks for. Also forced the folder name and location.
- **Guess the root** by scanning the home folder, or by noticing the terminal is
inside one. Scanning is slow and can find more than one. Tracking the terminal
folder depends on parsing `cd`, which misses other ways of moving.
- **Detect cloud-synced folders from the path.** Misses iCloud. A fixed hint is
shown instead.
2 changes: 1 addition & 1 deletion src/electron/exerciseProgress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function getExerciseProgress(): ProgressData {
}

function computeExerciseProgress(): ProgressData {
if (!getConfig().dataDirectory) return {};
if (!getConfig().exercisesRoot) return {};

let exerciseDirectory: string;
try {
Expand Down
38 changes: 16 additions & 22 deletions src/electron/ipc/config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { dialog, BrowserWindow } from "electron";
import { getConfig, saveConfig } from "../storage.js";
import { ipcMainHandle, ipcMainOn } from "../utils/util.js";
import { clearExerciseRoot, getConfig, saveConfig } from "../storage.js";
import { ipcMainHandle } from "../utils/util.js";
import {
getExerciseProgress,
resetExerciseProgressCache,
} from "../exerciseProgress.js";
import fs from "fs";
import path from "path";
import { resolveExerciseRootPick } from "../startPrereqs.js";

export function setupConfigIpc(mainWindow: BrowserWindow) {
ipcMainHandle("select-folder", async () => {
Expand All @@ -21,28 +20,23 @@ export function setupConfigIpc(mainWindow: BrowserWindow) {
return result.filePaths[0];
});

ipcMainOn("set-data-directory", ({ directory }) => {
console.log("[info] set-data-directory event: ", directory);
saveConfig({ dataDirectory: directory });
resetExerciseProgressCache();
ipcMainHandle("get-exercise-root", async () => {
return { root: getConfig().exercisesRoot ?? null };
});

ipcMainHandle("get-data-directory", async () => {
return getConfig().dataDirectory || null;
});
ipcMainHandle("set-exercise-root", async ({ directory }) => {
const resolved = resolveExerciseRootPick(directory);
if (!resolved.ok) return resolved;

ipcMainHandle("check-exercise-folder", async () => {
const dataDirectory = getConfig().dataDirectory || null;
if (!dataDirectory) {
return { dataDirectory: null, exercisesPath: null, ready: false };
}
saveConfig({ exercisesRoot: resolved.root });
resetExerciseProgressCache();
return resolved;
});

const exercisesPath = path.join(dataDirectory, "gitmastery-exercises");
return {
dataDirectory,
exercisesPath,
ready: fs.existsSync(exercisesPath),
};
ipcMainHandle("clear-exercise-root", async () => {
clearExerciseRoot();
resetExerciseProgressCache();
return true;
});

ipcMainHandle("get-downloaded-exercises", async () => {
Expand Down
Loading
Loading