Main#286
Conversation
Both imports were only referenced inside JSX comments, so tsc flagged them (TS6133/TS6192) and broke the build check on main since bd658af. The env.ts BooleanFromString errors mentioned in the issue do not reproduce: apps/native resolves effect to 3.21.0 (transitive) which still exports BooleanFromString. Only the root workspace pulls effect@4.0.0-beta.62, but env.ts lives under apps/native. Closes nixmac-62s.
The repo's bun.lock pins effect@4.0.0-beta.62 (no transitive effect@3.x). 4.0 removed Schema.BooleanFromString — which env.ts was using — so a fresh `bun install --frozen-lockfile` (what CI does) produces a tree where the import fails to compile. The earlier "doesn't reproduce locally" diagnosis on nixmac-62s was misled by a stale symlink at apps/native/node_modules/effect pointing into node_modules/.bun/effect@3.21.0/ left over from an older install run. Locally tsc resolved against the stale 3.x version, where BooleanFromString still exists. Migration: - Replace `Schema.BooleanFromString` with `Schema.Literals(["true", "false"])` (4.0's plural array form; 3.x had variadic `Schema.Literal(...)` — these are not interchangeable). - Coerce the validated string to `boolean | undefined` in code so the exported settings type matches the old BooleanFromString shape and downstream consumers (utils.ts:19's `!== true` check) don't need to change. - Drop the auto-derived `Schema.Schema.Type<typeof Settings>` because the runtime shape we expose (with the string→bool coercion) differs from the decoded shape. Strict parsing is preserved: any value other than "true"/"false" still fails at decode time, same as the original. Note: existing local installs with a stale apps/native/node_modules/effect symlink will need `bun install` to refresh — the stale 3.x effect doesn't have `Schema.Literals` (plural). One-time cost; matches CI from then on. Refs nixmac-62s.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Previous commit 0895711 accidentally included unresolved 'Updated upstream / Stashed changes' markers from a botched git stash pop. Re-exporting via bd export produces the correct merged state including nixmac-k8d as the fourth nixmac-srn blocker.
* feat(ci): nightly develop→main release with minor version bump
Adds a GitHub Actions cron (09:00 UTC daily) that merges develop into
main and tags a vMAJ.(MIN+1).0 release. Exits silently when develop has
no new commits vs main.
The actual ship work is delegated to build.yaml's existing `tag` mode —
the nightly job just produces the merge commit and the tag, then atomically
pushes both. compute-version.sh gets a small defensive edit: when HEAD is
already tagged with a v* tag, `release` mode is demoted to `branch` so the
main-push event doesn't trigger a duplicate (patch-bumped) ship alongside
the tag-push event.
Prerequisites for the cron to push successfully:
- RELEASE_BOT_TOKEN secret (PAT with bypass), OR
- repo ruleset bypass for github-actions[bot] on main
Without either, `git push --atomic` fails on the protected main branch.
Workflow falls back to GITHUB_TOKEN so cron + script wiring can be tested
before bypass is granted.
Files:
- .github/workflows/nightly-release.yaml (new)
- ops/scripts/release/nightly-release.sh (new; supports DRY_RUN=1)
- ops/scripts/release/compute-version.sh (tag-aware release-mode skip)
* feat(ci): scope nightly release trigger to native build affecteds
Replace the simple "any commit on develop" trigger with the no-turbo
equivalent of `turbo run build --affected --filter=native`. Releases now
fire only when develop has changes inside the native workspace, its
transitive workspace deps (currently @nixmac/ui), or global build inputs
(root package.json, bun.lock, root tsconfig, Cargo.toml/lock).
This skips nights where develop only got CI/docs/release-script changes
so a minor version isn't burned on commits that wouldn't ship anything
different to users.
Adds ops/scripts/release/affected-paths.mjs which dynamically resolves
the affected path set by reading the root workspaces config and walking
workspace:* deps — no hardcoded list to maintain when new packages
appear.
Verified locally:
- Real diff (apps/native/*, Cargo.lock, etc.): release
- Synthetic diff (ops/, .github/, docs/, *.md): skip
- Synthetic diff (bun.lock only): release (global input)
* fix(ci): address Copilot review feedback on nightly-release
- next_minor_version: filter to stable vMAJ.MIN.PATCH only so disposable
-test.N tags (used by build.yaml for signing rehearsals) can't pollute
the bump base
- Remote tag existence check: use `git ls-remote --exit-code` with an
exact ref pattern instead of `grep -q "${tag}"`, which would false-
positive when a longer tag like v1.2.0-test.1 contains the candidate
as a substring
- Docstring: align step 2 with actual behavior (logs a one-liner then
exits 0, not strictly silent)
- Workflow: add concurrency group so a scheduled run + manual dispatch
can't race on the same next-tag computation. cancel-in-progress=false
to avoid aborting a half-completed merge+tag mid-flight
* docs(ci): align skip-message wording with affected-paths policy
Copilot follow-up: three places still described the skip condition as
"no new commits vs main" or "skip silently". The actual policy is the
affected-paths filter (no-turbo equivalent of
`turbo run build --affected --filter=native`), which can return false
even when develop has commits — they just don't touch the native build
graph.
- should_release docstring: "skip silently" → "skip (caller logs reason)"
- main() skip log: "develop has no new work" → "no changes affect the
native build graph"
- workflow header: "no new commits vs main" → explanation of the
affected-paths filter and what kinds of develop activity will no-op
No behavior change.
* fix(ci): nightly-release cwd-independence and ..→... range
Copilot follow-up. Two real bugs:
1. The script claimed cwd-independence via REPO_ROOT but only used it for
the affected-paths.mjs invocation. Every git command and the
`node -p require('./package.json')` fallback ran relative to the
caller's cwd, so invoking the script from anywhere other than the
repo root would fail. main() now `cd`s into REPO_ROOT before doing
any work, which makes the cwd-independence claim actually true.
2. should_release used `git diff origin/main..origin/develop` (two-dot
range), which is tip-to-tip. If main has a hotfix not on develop,
two-dot treats it as a develop-side deletion and flags it as a
change — falsely triggering a release every night until the hotfix
gets merged back. Three-dot (`origin/main...origin/develop`) diffs
from the merge-base, so only commits new on develop are counted.
Verified locally: running the script from /tmp now produces the same
dry-run plan as running from the repo root. Two-dot vs three-dot on
current origin/main vs origin/develop differs by one file, confirming
there is currently a hotfix-style commit on main that the old check
would have erroneously counted.
* docs/fix(ci): nightly-release token fallback + optional-env docstring
Copilot follow-up:
1. Workflow checkout's token fallback used `secrets.GITHUB_TOKEN` as the
`||` second operand. While that *does* work in normal expression
contexts (it's GitHub's documented way to reference the auto-injected
token), the `github.token` context form is more reliable in fallback
positions and `if:` evaluations. Switched to
`secrets.RELEASE_BOT_TOKEN || github.token` for that defensive reason.
2. Header docstring claimed GIT_USER_NAME / GIT_USER_EMAIL were "Required
env" but the code uses `if [[ -n "${VAR:-}" ]]` guards and silently
skips git config when unset. They're actually optional CI overrides
that fall back to the caller's existing git config — fine for local
rehearsals, expected to be set in CI for bot-identity attribution.
Updated the comment to match the actual code.
No behavior change.
* fix(ci): defensive Copilot follow-ups on nightly-release
1. DRY_RUN expression now gates on `github.event_name == 'workflow_dispatch'`
before reading `inputs.dry_run`. The `inputs.*` context is only
populated for dispatch events; gating ensures scheduled cron runs
always get '0' regardless of how GitHub resolves missing-input refs.
2. compute-version.sh tag-skip regex now anchored with `$` so disposable
`v0.22.0-test.N` tags (used for signing/notarization rehearsals)
don't suppress legitimate `main`-push releases. Test tags don't ship
(publish/R2/Linear steps in build.yaml skip them), so main-push must
still bump+ship normally if such a tag happens to be at HEAD.
Verified: `v1.2.3` matches the regex (correctly skips release mode),
`v1.2.3-test.1` does not match (correctly does not suppress).
3. affected-paths.mjs now normalizes `package.json#workspaces` to handle
both the npm/bun array form and the Yarn object form
(`{ packages: [...] }`). nixmac uses the array form today; supporting
both is cheap future-proofing if the repo ever switches package
manager.
* docs/fix(ci): nightly-release docstring + unconditional fetch
Copilot follow-up:
1. Header step 4 said "Fast-forward / no-ff merge" but the implementation
always uses `git merge --no-ff`. Updated the comment to match the
actual behavior (always produces a merge commit so the release
boundary stays visible in git log --first-parent).
2. `git fetch` was DRY_RUN-gated, but should_release and
next_minor_version both query local refs (origin/main..origin/develop
diff + `git tag --list` for the latest stable). On a stale checkout
the dry-run could compute a wrong next version or false-positive
"nothing to release". Unwrap the fetch from `run` so it always
executes — fetch is read-only from the project's perspective (only
updates local origin/* refs), so unconditional execution is safe and
makes dry-run output reflect real remote state.
Verified: the unconditional fetch pulled a fresh v0.23.2 tag on this
run that the local checkout didn't have, validating the fix on its
first execution.
* fix(native): fix Chromatic Storybook
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(native): env.ts — drop version-skewed Schema.Literal for plain String
PR #195 build broke at TS2554 ("Expected 1 arguments, but got 2") after
80a19c6 reverted NIX_INSTALLED_OVERRIDE from `Schema.Literals([...])` to
`Schema.Literal(...)` (variadic). The problem: `Schema.Literal`'s signature
is fundamentally version-skewed —
effect 3.x: `Literal<L>(...values: L)` variadic, multi-value
effect 4.0-beta: `Literal<L>(value: L)` single value only,
use `Literals([...])` for sets
So neither form source-compiles in both:
- `Schema.Literal("true", "false")` works in 3.x, fails 4.0 (this PR)
- `Schema.Literals(["true", "false"])` works in 4.0, fails 3.x (Chromatic)
The repo's lockfile pins effect 4.0-beta, so tsc sees that — but the
Chromatic/storybook test environment can resolve 3.x via workspace
symlink hoisting, which is why 80a19c6 picked the 3.x form to fix
Chromatic and broke the build instead.
Fix: validate with plain `Schema.String` (signature is identical across
versions) and coerce to boolean in code via
`raw.NIX_INSTALLED_OVERRIDE === "true" ? true : undefined`. The only
downstream consumer is `settings.NIX_INSTALLED_OVERRIDE !== true` in
widget/utils.ts, which treats any non-"true" value as functionally
false — so silent acceptance of unexpected strings is observationally
equivalent to coercing them to undefined. We lose schema-level
"must be 'true' or 'false'" strictness; we gain working CI on both
the build and Chromatic paths.
Verified against both effect@3.21.0 and effect@4.0.0-beta.62 symlinks
in isolation: no TS2554, no missing-Literals errors.
* fix(ci): nightly-release DRY_RUN final-message clarity
Copilot follow-up: the final `echo "Released ${tag}"` ran unconditionally,
so workflow_dispatch dry-runs printed "Released v0.24.0" even though the
merge/tag/push steps were only echoed. Now branches on DRY_RUN: prints
"Dry run complete — would have released ${tag}" in dry mode, "Released
${tag}" in real mode. Behavior preserved; just the operator-facing log
line distinguishes the two modes.
Did not touch the `local changed paths file path` declaration in
should_release — Copilots claim that `path` is unused is incorrect.
`path` is the inner loops read variable on line 85 and is used in the
path-matching comparisons on lines 87, 89, 91, and 92.
* fix(ci): nightly-release working-tree guard + setup-node pin
Copilot follow-up:
1. Add a clean-working-tree guard before the destructive `git reset --hard
origin/${MAIN_BRANCH}`. Local accidental invocations would otherwise
silently obliterate uncommitted work. CI runners start clean by
construction so the guard never fires in CI; DRY_RUN=1 bypasses it
since dry-mode does not mutate the tree. Error path prints
`git status --short` so the operator can see what would have been lost.
2. Add an actions/setup-node@v6 step pinned to node-version: 20 (matching
build.yaml's convention). The release script invokes node for
affected-paths.mjs and the package.json version fallback in
next_minor_version. Without an explicit pin the cron would depend on
whatever node the ubuntu-latest runner image ships, which is not stable
across image updates.
Verified: bash -n passes, dry-run still works, guard simulation triggers
correctly on dirty tree and stays out of the way in DRY_RUN=1.
---------
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cooper <czxtm@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cooper <czxtm@users.noreply.github.com>
* Fix diff line stats display Co-authored-by: cooper <czxtm@users.noreply.github.com> * Update diff UI snapshots Co-authored-by: cooper <czxtm@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cooper <czxtm@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cooper <czxtm@users.noreply.github.com>
* Add configurable evolution output token limit Co-authored-by: cooper <czxtm@users.noreply.github.com> * Update AI settings snapshot for output token limit Co-authored-by: cooper <czxtm@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cooper <czxtm@users.noreply.github.com>
* fix(settings): rename untracked settings indicator Co-authored-by: cooper <czxtm@users.noreply.github.com> * test(settings): update untracked settings snapshots Co-authored-by: cooper <czxtm@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cooper <czxtm@users.noreply.github.com>
Reorder workflow steps so build/deploy/comment run BEFORE snapshot tests. Previously, when snapshot tests failed, the build, deploy, and PR comment steps were skipped—no preview URL was posted. Also includes: - Add deployments:write + issues:write permissions for deployment API - Add deploy URL validation (fail loudly on missing URL) - Add Publish Storybook deployment link step (GitHub Deployments API) - Fix comment step: use always() condition so it runs even if snapshot tests fail, and link the URL as markdown - Add develop to push triggers - Show preview link in step summary regardless of test outcome
There was a problem hiding this comment.
Pull request overview
This PR updates nixmac’s UI, e2e harness, and release/CI automation around (1) renaming “untracked macOS customizations” to “untracked settings”, (2) making local providers (Ollama/vLLM) require explicit model names and adding a configurable “Max output tokens” limit that’s wired through UI → prefs → Rust providers, and (3) adding nightly release automation + improving Storybook CI (deploy preview links + snapshots).
Changes:
- Rename the managed system defaults indicator/badge copy from “customizations” to “settings” and update e2e selectors/patterns accordingly.
- Add “Max output tokens” preference (default 32,768) and enforce explicit model selection for local providers; propagate into Rust provider requests and improve user-facing context-window error guidance.
- Add release automation scripts/workflows (nightly develop→main merge + tag) and UI polish (diff line stats badges; untracked card theming/icons).
Reviewed changes
Copilot reviewed 59 out of 59 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/computer-use-e2e/run-remote-cua.mjs | Updates CUA badge text matching for renamed “untracked settings” copy. |
| tests/e2e/scenarios/macos_customization_save_rollback_smoke.sh | Renames scenario strings/patterns for “untracked settings” badge. |
| tests/e2e/lib/nixmac_managed_badge_proof.sh | Updates managed-badge click/query patterns and visible badge regex for settings wording. |
| README.md | Documents vLLM env vars and max-output-tokens guidance + CLI example. |
| ops/scripts/release/nightly-release.sh | New script to merge develop→main, tag next minor version, and push atomically when affected. |
| ops/scripts/release/compute-version.sh | Avoids double-builds by detecting stable tag already pointing at HEAD on main push. |
| ops/scripts/release/affected-paths.mjs | New script to compute affected workspace/global paths (no-turbo “affected” equivalent). |
| apps/native/src/lib/env.ts | Adjusts Effect schema usage and coerces NIX_INSTALLED_OVERRIDE env var. |
| apps/native/src/lib/constants.ts | Adds DEFAULT_MAX_OUTPUT_TOKENS constant for UI defaults. |
| apps/native/src/lib/ai-provider-validation.ts | Requires model names for local providers (Ollama/vLLM) and base URL for vLLM. |
| apps/native/src/lib/ai-provider-validation.test.ts | Adds tests asserting local providers require explicit model names. |
| apps/native/src/ipc/types.ts | Extends UiPrefs/UiPrefsUpdate types with maxOutputTokens. |
| apps/native/src/hooks/use-rollback.ts | Refreshes change map timing around rollback/finalize flow. |
| apps/native/src/hooks/use-rollback.test.ts | Adds coverage for rollback rebuild-success sequencing and change-map refresh. |
| apps/native/src/components/widget/summaries/hunk-pill.tsx | Replaces inline diff counting with shared diff-line-stats component and optional count rendering. |
| apps/native/src/components/widget/summaries/full-file-diff-editor.tsx | Adds per-file diff stats badge and adjusts hunk pill count display behavior. |
| apps/native/src/components/widget/summaries/diff-line-stats.tsx | New helper + badge component to compute/render added/removed line stats from diffs. |
| apps/native/src/components/widget/summaries/diff-line-stats.test.tsx | Tests for diff-line-stats counting/summing behavior. |
| apps/native/src/components/widget/summaries/snapshots/hunk-pill.stories.tsx.snap | Updates Storybook snapshots for new hunk pill rendering. |
| apps/native/src/components/widget/summaries/snapshots/full-file-diff-editor.stories.tsx.snap | Updates Storybook snapshots for file-level stats badge rendering. |
| apps/native/src/components/widget/summaries/snapshots/diff-section.stories.tsx.snap | Updates Storybook snapshots for diff section badges. |
| apps/native/src/components/widget/steps/setup-step.tsx | Fixes “Next” behavior to save the currently displayed host even if not reselected. |
| apps/native/src/components/widget/steps/setup-step.test.tsx | Adds test covering “Next” saving displayed host without reselect. |
| apps/native/src/components/widget/settings/settings-dialog.tsx | Threads maxOutputTokens into settings form defaults and tab props. |
| apps/native/src/components/widget/settings/ai-models-tab.tsx | Adds Max output tokens input + enforces local-model placeholders and validation wiring. |
| apps/native/src/components/widget/settings/ai-models-tab.stories.tsx | Updates fixture with maxOutputTokens field. |
| apps/native/src/components/widget/settings/snapshots/ai-models-tab.stories.tsx.snap | Updates Storybook snapshot for the new limits field. |
| apps/native/src/components/widget/promptinput/system-defaults-cta.tsx | Renames badge copy to “untracked settings”, swaps icon, and centralizes formatting. |
| apps/native/src/components/widget/promptinput/system-defaults-cta.test.tsx | Adds tests for singular/plural copy and absence behavior. |
| apps/native/src/components/widget/promptinput/prompt-input.tsx | Tightens provider validation by passing model; changes dirty-tree behavior to open warning dialog. |
| apps/native/src/components/widget/promptinput/prompt-input.test.tsx | Adds test ensuring the dirty-tree dialog opens instead of starting evolve/adopt. |
| apps/native/src/components/widget/filesystem/untracked-card.tsx | Adds tone-based styling and icon resolution for untracked cards. |
| apps/native/src/components/widget/filesystem/icons.ts | Adds “settings” icon mapping. |
| apps/native/src/components/widget/filesystem/data.ts | Renames “settings differ from defaults” to “untracked settings” and updates icon/tone. |
| apps/native/src/components/widget/filesystem/snapshots/untracked-card.stories.tsx.snap | Updates snapshots for new untracked card theming/icon/copy. |
| apps/native/src/components/widget/filesystem/snapshots/seed-prompt.stories.tsx.snap | Updates seed prompt snapshot to “untracked settings”. |
| apps/native/src-tauri/src/utils.rs | Adds non_empty_trimmed_string helper + tests. |
| apps/native/src-tauri/src/system/permissions.rs | Tightens skip-permissions flags (debug-only; truthy parsing) + tests. |
| apps/native/src-tauri/src/summarize/templates/evolve_group_description_rules.md | Adds guardrail against inventing broader intent than visible hunks support. |
| apps/native/src-tauri/src/summarize/templates/base_preamble.md | Notes summaries are based on patch hunks, not full diffs. |
| apps/native/src-tauri/src/summarize/templates/base_hunk_description_rules.md | Adds rule to avoid guessing broader intent with incomplete context. |
| apps/native/src-tauri/src/summarize/templates/base_changes_intro.md | Reinforces “only summarize what is visible in hunks” constraint. |
| apps/native/src-tauri/src/summarize/build_prompt.rs | Adds tests ensuring prompts include the new scope guardrails. |
| apps/native/src-tauri/src/storage/store.rs | Adds DEFAULT_MAX_OUTPUT_TOKENS and get/set for maxOutputTokens pref. |
| apps/native/src-tauri/src/shared_types/prefs.rs | Extends shared prefs structs with max_output_tokens. |
| apps/native/src-tauri/src/main.rs | Threads max_output_tokens through CLI mode invocation. |
| apps/native/src-tauri/src/evolve/providers/openai.rs | Makes max output tokens configurable instead of hardcoded 65k. |
| apps/native/src-tauri/src/evolve/providers/ollama.rs | Plumbs max output tokens into Ollama options (num_predict). |
| apps/native/src-tauri/src/evolve/providers/mod.rs | Adds context-window error detection and improved user_message guidance. |
| apps/native/src-tauri/src/evolve/mod.rs | Adds max-output-tokens normalization, requires explicit local models for Ollama/vLLM, and wires token limit through providers. |
| apps/native/src-tauri/src/commands/ui_prefs.rs | Exposes max_output_tokens in getPrefs/setPrefs. |
| apps/native/src-tauri/src/cli.rs | Adds CLI flag --max-output-tokens and includes it in combined output JSON. |
| apps/native/src-tauri/src/ai/providers/mod.rs | Requires explicit local summary models for Ollama/vLLM and trims/filters empty model strings. |
| apps/native/.storybook/mocks/tauri-runtime.ts | Adds maxOutputTokens to Storybook mocked prefs. |
| apps/native/.gitignore | Ignores apps/native/private/ directory. |
| .github/workflows/storybook.yaml | Runs on develop pushes too; deploys Storybook preview and publishes/comment links; snapshot tests moved later. |
| .github/workflows/nightly-release.yaml | New nightly cron workflow to run nightly-release.sh with appropriate token handling. |
| .beads/issues.jsonl | Adds/tracks issues related to CI/build and the nightly release effort. |
| NIX_INSTALLED_OVERRIDE: | ||
| rawSettings.NIX_INSTALLED_OVERRIDE == null | ||
| ? undefined | ||
| : /^true$/i.test(String(rawSettings.NIX_INSTALLED_OVERRIDE)), | ||
| } as { | ||
| readonly VITE_SERVER_URL?: string; | ||
| readonly NIX_INSTALLED_OVERRIDE?: boolean; | ||
| raw.NIX_INSTALLED_OVERRIDE === "true" ? true : undefined, |
| /// `Http { status, body }` before reaching this method. | ||
| pub fn user_message(&self) -> String { | ||
| match self { | ||
| ProviderError::Http { status, body } if looks_like_context_window_error(body) => { |
| "untracked settings|untracked setting" \ | ||
| "customization" \ | ||
| "feat(e2e): import untracked macos customizations" |
📋 PR Overview
|
Summary
Test Plan
Docs
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.