fix theme picker integration test; one bake for build-time facts (T-116)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
The test was awaiting services.commands.execute('theme.pick') whose
Future doesn't complete until the dialog is dismissed — deadlock.
Fire-and-forget around pumpAndSettle, then tap Cancel, then await
the original future. Also tear the widget tree down before
services.dispose() so listening widgets unsubscribe first.
Pre-existing layout overflow in the welcome _StatusLine surfaced
when running the test at narrower viewports. Switched to a whole-
row FittedBox(scaleDown) — uniform shrink on narrow screens, no-op
at standard widths.
User flagged the hardcoded 'clide 2.0.0-dev' string. Replaced with
one generated lib/src/build_info.g.dart (gitignored, regenerated
by `make gen-build-info` from pubspec.yaml + git short SHA + UTC
clock). The same target re-syncs assets/licenses.yaml self.version
in place — no second source. Every make build/run/test depends on
it implicitly. Welcome status line now reads `clideVersion`. Stale
fontSize literals in welcome_view swept to typography constants;
clideFontMeta=13, clideFontDialogTitle=16, clideFontWelcomeBanner=52
added to fill gaps in the scale.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -93,9 +93,10 @@ Cutting a release is its own commit. In a single commit:
|
||||
1. Move all entries from `## [Unreleased]` under a new heading `## [X.Y.Z] — YYYY-MM-DD`.
|
||||
2. Leave an empty `## [Unreleased]` section at the top with its subsection skeleton ready.
|
||||
3. Bump `pubspec.yaml` `version:` to `X.Y.Z` (drop the `-dev` suffix for the tag; re-add it on the next development commit if desired).
|
||||
4. Commit subject: `release vX.Y.Z`.
|
||||
4. Run `make gen-build-info` so `assets/licenses.yaml` `self.version:` re-syncs from pubspec (it's auto-rewritten by every build but commit the fresh state). Stage the resulting diff alongside step 3.
|
||||
5. Commit subject: `release vX.Y.Z`.
|
||||
|
||||
`pubspec.yaml` is the single source of truth for the version — the Makefile reads it for ldflag stamping of the sidecar binary, and the Flutter app reads it for build info. Bumping `pubspec.yaml` and the changelog out of sync is the mistake this rule prevents.
|
||||
`pubspec.yaml` is the single source of truth for the version. Every `make` build/run/test target regenerates `lib/src/build_info.g.dart` (gitignored) and rewrites `assets/licenses.yaml` `self.version:` from it — so the Flutter app sees the current version everywhere without manual sync. Bumping `pubspec.yaml` and the changelog out of sync is the mistake this rule prevents.
|
||||
|
||||
## Attribution trailer
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ tools/ui/.serve.pid
|
||||
# Lives under test/ so the repo root stays uncluttered.
|
||||
/test/.test-output/
|
||||
|
||||
# -- Build-time stamps generated by `make gen-build-info` -----------
|
||||
# version (from pubspec.yaml), commit (git short SHA), date (UTC).
|
||||
# Regenerated on every make build/run/test target.
|
||||
/lib/src/build_info.g.dart
|
||||
|
||||
# -- Test, coverage, profile output ------------------------------------
|
||||
*.test
|
||||
*.out
|
||||
|
||||
@@ -1818,3 +1818,6 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'status', 'in_progress', 'done', NULL, '2026-05-18 08:26:05', '2026-05-18 08:26:05', '2026-05-18 08:26:05', NULL, 'b8978359fc1b83ea0bcfc6cd5d34d5de', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-115', 'status', 'done', 'done', NULL, '2026-05-18 09:30:59', '2026-05-18 09:30:59', '2026-05-18 09:30:59', NULL, '2039c525646caaf89a735454f7278162', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 10:27:25', '2026-05-18 10:27:25', '2026-05-18 10:27:25', NULL, 'e40d2c9ddd9915ad2e32302bd555c99f', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'status', 'in_progress', 'done', NULL, '2026-05-18 10:30:30', '2026-05-18 10:30:30', '2026-05-18 10:30:30', NULL, 'd5ecc2578b5b48991d07531eb2166529', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-116', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 10:35:47', '2026-05-18 10:35:47', '2026-05-18 10:35:47', NULL, '59b70e93c913952d4f13b9ad2320e060', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-116', 'status', 'in_progress', 'done', NULL, '2026-05-18 11:22:46', '2026-05-18 11:22:46', '2026-05-18 11:22:46', NULL, '16fd285587d7a37090d2a21f4307c8e8', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -1969,3 +1969,20 @@ Done when:
|
||||
- No new public surface; everything stays library-private
|
||||
|
||||
Source: T-107 / consultants.md "Code quality — Findings — [Major]".', 'backlog', 'low', NULL, NULL, NULL, '2026-05-18 10:29:02', '2026-05-18 10:29:02', NULL, '1ac51fb8f92a9e72903c16e31f076955', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-107', 'task', 'T-97', 'decide lib/src/terminal/ status: formally vendor or clean to project bar', '~7k LOC forked from xterm.dart in an undeclared middle state. Carries commented-out `print()` debugging (`custom_text_edit.dart:244-275`), dangling TODOs (`parser.dart:110-113`, `keytab.dart:91`), a 1137-line `parser.dart`, and the only `// ignore: invalid_use_of_protected_member` in the repo (`terminal_view.dart:363`). Memory says "code under `lib/` is owned, not vendored."
|
||||
|
||||
**Decide one path and execute:**
|
||||
- **(a)** Formally vendor: relocate to `native/` or document as frozen in a D-record + `licenses.yaml`. Coverage carve-out documented.
|
||||
- **(b)** Clean to the project bar: remove debug prints, resolve TODOs, justify or remove the protected-member suppression, consider splitting `parser.dart`.
|
||||
|
||||
Source: consultants.md "Code quality — Findings — [Major]".', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 18:47:55', '2026-05-18 10:30:30', NULL, '930012b3c62d18d9fe242c7d5879a347', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-116', 'bug', 'T-97', 'integration_test/theme_picker_test.dart hangs pumpAndSettle on theme.pick', 'When running `flutter test integration_test/theme_picker_test.dart` against the real boot path, calling `services.commands.execute(''theme.pick'')` followed by `await tester.pumpAndSettle()` does not settle — the test hangs for 60+ seconds and never reaches the `Select theme` / `Cancel` assertions. Likely an animation loop or a never-completing Future in the modal mount path.
|
||||
|
||||
Blocks T-103 from adding integration tests to `make push-check` (the other two integration tests pass after viewport + assertion fixes).
|
||||
|
||||
**Acceptance:**
|
||||
1. `flutter test integration_test/theme_picker_test.dart` completes in <10s.
|
||||
2. Both assertions (`Select theme` visible after `theme.pick`; gone after `Cancel`) pass.
|
||||
3. T-103''s `push-check-full` can include integration tests without the theme-picker hanging.
|
||||
|
||||
Source: surfaced 2026-05-17 during T-103 work.', 'done', 'medium', NULL, NULL, NULL, '2026-05-17 19:14:31', '2026-05-18 11:22:46', NULL, 'ff3b728310bd265a57a60778417d026f', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
@@ -111,6 +111,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
softer ink-tinted shadow (T-114).
|
||||
- Text-zoom (Ctrl +/-/0) is now a kernel `TextZoom` service and shows
|
||||
up in the palette as `View: Zoom In/Out/Reset Zoom` (T-114).
|
||||
- `make gen-build-info` bakes `lib/src/build_info.g.dart` (version,
|
||||
commit, date) and rewrites `assets/licenses.yaml` `self.version:`
|
||||
from `pubspec.yaml` on every build/run/test target — one source of
|
||||
truth, no manual sync, no `--dart-define` plumbing.
|
||||
- Panel splitters (sidebar / context / editor-split) are tab-focusable;
|
||||
arrow keys nudge by 10 px, Shift+arrow by 50 px (2% / 10% for the
|
||||
editor split). Exposed as slider Semantics nodes so screen readers
|
||||
@@ -182,6 +186,15 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Welcome status line no longer overflows on narrow viewports —
|
||||
whole-row `FittedBox(scaleDown)` instead of fixed sibling widths.
|
||||
Inline `fontSize:` literals replaced with the typography
|
||||
constants. Version label reads `clideVersion` so the status line
|
||||
stays in sync with `pubspec.yaml` (T-116).
|
||||
- Integration test `theme_picker_test.dart` no longer deadlocks —
|
||||
was `await`ing `services.commands.execute('theme.pick')` whose
|
||||
Future doesn't complete until the dialog is dismissed. Now
|
||||
fire-and-forget around `pumpAndSettle` (T-116).
|
||||
- `TerminalView.onTapUp` now actually fires on primary tap — was
|
||||
wired to a dead code path (T-93). Dead `onTapUp` surface on
|
||||
`TerminalGestureHandler` / `TerminalGestureDetector` removed.
|
||||
|
||||
@@ -19,6 +19,7 @@ else
|
||||
endif
|
||||
|
||||
VERSION ?= $(shell awk -F': *' '/^version:/ {gsub(/[" ]/,"",$$2); print $$2; exit}' pubspec.yaml)
|
||||
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
.PHONY: help
|
||||
@@ -28,7 +29,7 @@ help: ## Show this help.
|
||||
# -- app (Flutter) -------------------------------------------------------
|
||||
|
||||
.PHONY: run
|
||||
run: ## Launch the Flutter desktop app.
|
||||
run: gen-build-info ## Launch the Flutter desktop app.
|
||||
ifeq ($(FLUTTER_OS),linux)
|
||||
GDK_BACKEND=x11 LD_LIBRARY_PATH=$(CURDIR)/native/linux-x64$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH} flutter run -d linux --dart-define=CLIDE_PROJECT=$(CURDIR)
|
||||
else
|
||||
@@ -64,7 +65,7 @@ pubget: ## flutter pub get.
|
||||
flutter pub get
|
||||
|
||||
.PHONY: build-check
|
||||
build-check: ## Verify native + Dart build compiles (no run).
|
||||
build-check: gen-build-info ## Verify native + Dart build compiles (no run).
|
||||
ifeq ($(FLUTTER_OS),linux)
|
||||
LD_LIBRARY_PATH=$(CURDIR)/native/linux-x64$${LD_LIBRARY_PATH:+:$$LD_LIBRARY_PATH} flutter build linux
|
||||
else
|
||||
@@ -79,29 +80,45 @@ analyze: ## flutter analyze.
|
||||
format: ## dart format --set-exit-if-changed.
|
||||
dart format --set-exit-if-changed .
|
||||
|
||||
# The single bake of every build-time fact derived from pubspec.yaml
|
||||
# + git + clock. Runs implicitly as a prereq of every target that
|
||||
# compiles or executes Dart code so nobody has to remember it.
|
||||
#
|
||||
# Outputs:
|
||||
# - lib/src/build_info.g.dart — gitignored, fresh on every build.
|
||||
# Exposes `clideVersion` (from pubspec), `clideCommit` (git short
|
||||
# SHA), `clideDate` (UTC now) for the app to read directly.
|
||||
# - assets/licenses.yaml `self.version:` — rewritten in place so the
|
||||
# bundled license manifest never drifts from pubspec. (Tracked in
|
||||
# git; the rewrite is a no-op when in sync.)
|
||||
.PHONY: gen-build-info
|
||||
gen-build-info:
|
||||
@printf '// GENERATED — do not edit. Regenerated by `make` on every\n// build/run/test (see gen-build-info in Makefile). The version\n// field is read from pubspec.yaml `version:` — the single source\n// of truth for the release number; commit + date stamp at run time.\nconst String clideVersion = '"'"'%s'"'"';\nconst String clideCommit = '"'"'%s'"'"';\nconst String clideDate = '"'"'%s'"'"';\n' "$(VERSION)" "$(COMMIT)" "$(DATE)" > lib/src/build_info.g.dart
|
||||
@awk -v v="$(VERSION)" '/^self:/ {in_self=1} in_self && /^[[:space:]]+version:/ {sub(/version:.*/, "version: \"" v "\""); in_self=0} {print}' assets/licenses.yaml > assets/licenses.yaml.tmp && mv assets/licenses.yaml.tmp assets/licenses.yaml
|
||||
|
||||
.PHONY: verify
|
||||
verify: analyze format decisions-validate changelog-gate ## No-tests sweep — analyze + format + decisions-validate + changelog-gate. For mid-edit "are the gates green?" checks; `push-check` is the full pre-push pipeline.
|
||||
verify: gen-build-info analyze format decisions-validate changelog-gate ## No-tests sweep — gen-build-info + analyze + format + decisions-validate + changelog-gate. For mid-edit "are the gates green?" checks; `push-check` is the full pre-push pipeline.
|
||||
|
||||
.PHONY: t
|
||||
t: ## Run one test path with tee'd output. Usage: make t T=test/path/to/spec.dart
|
||||
t: gen-build-info ## Run one test path with tee'd output. Usage: make t T=test/path/to/spec.dart
|
||||
@mkdir -p test/.test-output
|
||||
@if [ -z "$(T)" ]; then echo "usage: make t T=test/path/to/spec.dart" >&2; exit 2; fi
|
||||
flutter test $(T) 2>&1 | tee test/.test-output/last.log
|
||||
|
||||
.PHONY: test
|
||||
test: ## Fast: analyze + format + unit + widget + golden (<60s).
|
||||
test: gen-build-info ## Fast: analyze + format + unit + widget + golden (<60s).
|
||||
ci/test.sh
|
||||
|
||||
.PHONY: test-core
|
||||
test-core: ## Core subsystem tests (IPC, PTY, git, pane registry).
|
||||
test-core: gen-build-info ## Core subsystem tests (IPC, PTY, git, pane registry).
|
||||
ci/test_core.sh
|
||||
|
||||
.PHONY: test-a11y
|
||||
test-a11y: ## A11y contract (semantic coverage + keyboard + contrast + i18n).
|
||||
test-a11y: gen-build-info ## A11y contract (semantic coverage + keyboard + contrast + i18n).
|
||||
ci/test_a11y.sh
|
||||
|
||||
.PHONY: test-integration
|
||||
test-integration: ## Integration tests (real app boot; xvfb on headless Linux).
|
||||
test-integration: gen-build-info ## Integration tests (real app boot; xvfb on headless Linux).
|
||||
ci/test_integration.sh
|
||||
|
||||
.PHONY: test-e2e
|
||||
@@ -141,15 +158,15 @@ ui-smoke: ## Build + serve + run Playwright smoke + stop.
|
||||
@sh -c 'trap "tools/ui/stop.sh >/dev/null 2>&1" EXIT; cd tools/ui && npx playwright test smoke.spec.ts'
|
||||
|
||||
.PHONY: build
|
||||
build: ## flutter build for the current OS.
|
||||
build: gen-build-info ## flutter build for the current OS.
|
||||
flutter build $(FLUTTER_OS)
|
||||
|
||||
.PHONY: build-linux
|
||||
build-linux: ## flutter build linux (desktop bundle).
|
||||
build-linux: gen-build-info ## flutter build linux (desktop bundle).
|
||||
flutter build linux
|
||||
|
||||
.PHONY: build-macos
|
||||
build-macos: ## flutter build macos (desktop bundle).
|
||||
build-macos: gen-build-info ## flutter build macos (desktop bundle).
|
||||
flutter build macos
|
||||
|
||||
# -- install / uninstall -----------------------------------------------------
|
||||
@@ -263,7 +280,7 @@ decisions-validate: ## Parser dry-run over governance/{decisions,questions,rejec
|
||||
push-check: decisions-validate test-core test test-a11y coverage-gate changelog-gate ## Pre-push gate (fast — <2 min target).
|
||||
|
||||
.PHONY: push-check-full
|
||||
push-check-full: push-check test-integration smoke-bundle ## Pre-release gate (push-check + integration + smoke; slower; skips theme_picker per T-116).
|
||||
push-check-full: push-check test-integration smoke-bundle ## Pre-release gate (push-check + integration + smoke; slower).
|
||||
|
||||
.PHONY: hooks
|
||||
hooks: ## Install the repo's git hooks.
|
||||
|
||||
@@ -36,7 +36,10 @@ schema_version: 1
|
||||
# About screen can read it at runtime.
|
||||
self:
|
||||
name: clide
|
||||
version: "2.0.0-dev"
|
||||
# Auto-synced from pubspec.yaml `version:` by `make gen-build-info`
|
||||
# (runs implicitly on every build/run/test). Don't hand-edit; bump
|
||||
# pubspec instead.
|
||||
version: "2.1.0"
|
||||
homepage: https://github.com/postmeridiem/clide
|
||||
license: MIT
|
||||
license_file: assets/LICENSE
|
||||
|
||||
@@ -3,16 +3,10 @@
|
||||
# start" regression gate. Flutter integration tests prefer one file at
|
||||
# a time on desktop; we iterate to avoid the "Unable to start the app"
|
||||
# error that hits when they run as a batch.
|
||||
#
|
||||
# Skips: theme_picker_test.dart — pumpAndSettle hangs on theme.pick
|
||||
# (T-116). Restore once that's fixed.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
for f in integration_test/*_test.dart; do
|
||||
case "$f" in
|
||||
integration_test/theme_picker_test.dart) echo "==> integration_test: $f (SKIPPED — T-116)"; continue ;;
|
||||
esac
|
||||
echo "==> integration_test: $f"
|
||||
flutter test "$f"
|
||||
done
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:clide/builtin/theme_picker/theme_picker.dart';
|
||||
import 'package:clide/builtin/welcome/welcome.dart';
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -39,21 +40,39 @@ void main() {
|
||||
..register(ThemePickerExtension());
|
||||
await services.extensions.activateAll();
|
||||
|
||||
// Larger viewport so the welcome screen's _StatusLine row doesn't
|
||||
// overflow on the default ~800x600 — that overflow throws a layout
|
||||
// assertion that fails the test before we get to theme.pick.
|
||||
tester.view.physicalSize = const Size(1200, 800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
await tester.pumpWidget(ClideApp(services: services));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Invoke the command.
|
||||
await services.commands.execute('theme.pick');
|
||||
// Fire-and-forget: theme.pick's run handler awaits
|
||||
// ctx.dialog.show(...), whose Future doesn't complete until the
|
||||
// dialog is dismissed. Awaiting here would deadlock the test
|
||||
// before the dialog ever mounts.
|
||||
final pending = services.commands.execute('theme.pick');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Select theme'), findsOneWidget);
|
||||
expect(find.text('Cancel'), findsOneWidget);
|
||||
|
||||
// Dismiss via Cancel.
|
||||
// Dismiss via Cancel — this completes the pending future above.
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Select theme'), findsNothing);
|
||||
await pending;
|
||||
|
||||
// Tear the widget tree down BEFORE disposing services so widgets
|
||||
// that listen to kernel notifiers (KeymapService, etc.) unsubscribe
|
||||
// first. Disposing services while the tree is mounted triggers
|
||||
// "ChangeNotifier used after dispose" during teardown rebuilds.
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pumpAndSettle();
|
||||
await services.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:clide/clide.dart' show clideVersion;
|
||||
import 'package:clide/kernel/kernel.dart';
|
||||
import 'package:clide/widgets/widgets.dart';
|
||||
import 'package:flutter/services.dart' show MissingPluginException;
|
||||
@@ -90,7 +91,7 @@ class _TipsCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClideText('TIPS', fontSize: 12, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
ClideText('TIPS', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
const SizedBox(height: 14),
|
||||
_tipRow(firstRow),
|
||||
const SizedBox(height: 8),
|
||||
@@ -108,8 +109,8 @@ class _TipsCard extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: ClideText(tips[i].$1, fontSize: 13, color: tokens.globalTextMuted)),
|
||||
ClideText(tips[i].$2, fontSize: 12, color: tokens.globalForeground, fontFamily: clideMonoFamily),
|
||||
Expanded(child: ClideText(tips[i].$1, fontSize: clideFontMeta, color: tokens.globalTextMuted)),
|
||||
ClideText(tips[i].$2, fontSize: clideFontSmall, color: tokens.globalForeground, fontFamily: clideMonoFamily),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -134,8 +135,8 @@ class _Header extends StatelessWidget {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClideText('clide', fontSize: 52, fontWeight: FontWeight.w300, color: tokens.globalForeground),
|
||||
ClideText('IDE for Claude Code CLI', muted: true, fontSize: 16),
|
||||
ClideText('clide', fontSize: clideFontWelcomeBanner, fontWeight: FontWeight.w300, color: tokens.globalForeground),
|
||||
ClideText('IDE for Claude Code CLI', muted: true, fontSize: clideFontDialogTitle),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -153,7 +154,7 @@ class _StartColumn extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClideText('START', fontSize: 12, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
ClideText('START', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
const SizedBox(height: 20),
|
||||
_ActionRow(
|
||||
icon: PhosphorIcons.folder,
|
||||
@@ -236,8 +237,8 @@ class _ActionRow extends StatelessWidget {
|
||||
children: [
|
||||
ClideIcon(icon, size: 18, color: tokens.globalTextMuted),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(child: ClideText(label, fontSize: 15, color: tokens.globalForeground)),
|
||||
if (shortcut != null) ClideText(shortcut!, fontSize: 13, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
Expanded(child: ClideText(label, fontSize: clideFontBody, color: tokens.globalForeground)),
|
||||
if (shortcut != null) ClideText(shortcut!, fontSize: clideFontMeta, color: tokens.globalTextMuted, fontFamily: clideMonoFamily),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -259,10 +260,10 @@ class _RecentColumn extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClideText('RECENT', fontSize: 12, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
ClideText('RECENT', fontSize: clideFontSmall, color: tokens.sidebarSectionHeader, fontFamily: clideMonoFamily),
|
||||
const SizedBox(height: 20),
|
||||
if (recents.isEmpty)
|
||||
const ClideText('No recent projects.', muted: true, fontSize: 14)
|
||||
const ClideText('No recent projects.', muted: true, fontSize: clideFontCaption)
|
||||
else
|
||||
for (final r in recents)
|
||||
_RecentRow(
|
||||
@@ -312,18 +313,18 @@ class _RecentRow extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClideText(project.name, fontSize: 15, fontWeight: FontWeight.w500),
|
||||
ClideText(project.name, fontSize: clideFontBody, fontWeight: FontWeight.w500),
|
||||
const SizedBox(height: 3),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: ClideText(project.relativePath,
|
||||
muted: true, fontSize: 13, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)),
|
||||
muted: true, fontSize: clideFontMeta, fontFamily: clideMonoFamily, maxLines: 1, overflow: TextOverflow.ellipsis)),
|
||||
if (project.branch != null) ...[
|
||||
ClideText(' · ', muted: true, fontSize: 13),
|
||||
ClideText(' · ', muted: true, fontSize: clideFontMeta),
|
||||
ClideIcon(PhosphorIcons.gitBranch, size: 11, color: tokens.globalTextMuted),
|
||||
const SizedBox(width: 3),
|
||||
ClideText(project.branch!, muted: true, fontSize: 13, fontFamily: clideMonoFamily),
|
||||
ClideText(project.branch!, muted: true, fontSize: clideFontMeta, fontFamily: clideMonoFamily),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -337,7 +338,7 @@ class _RecentRow extends StatelessWidget {
|
||||
onTap: onToggleSticky,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ClideText(project.timeAgo, muted: true, fontSize: 13),
|
||||
ClideText(project.timeAgo, muted: true, fontSize: clideFontMeta),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -390,20 +391,29 @@ class _StatusLine extends StatelessWidget {
|
||||
listenable: kernel.toolchain,
|
||||
builder: (ctx, _) {
|
||||
final tc = kernel.toolchain;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
// FittedBox+scaleDown shrinks the row uniformly on narrow
|
||||
// viewports rather than overflowing — at standard widths it's
|
||||
// a no-op. The status line is decorative chrome; keep it on
|
||||
// one line by accepting a tiny font on very narrow screens.
|
||||
return FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClideText('clide 2.0.0-dev', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
||||
ClideText(' · ', muted: true, fontSize: 12),
|
||||
ClideText('clide $clideVersion', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily),
|
||||
ClideText(' · ', muted: true, fontSize: clideFontSmall),
|
||||
if (!tc.resolved)
|
||||
ClideText('checking…', muted: true, fontSize: 12, fontFamily: clideMonoFamily)
|
||||
ClideText('checking…', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily)
|
||||
else if (tc.allOk)
|
||||
ClideText('application ok', fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusSuccess)
|
||||
ClideText('application ok', fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusSuccess)
|
||||
else
|
||||
ClideText(tc.missing.map((t) => '$t not found').join(' · '), fontSize: 12, fontFamily: clideMonoFamily, color: tokens.statusWarning),
|
||||
ClideText(' · ', muted: true, fontSize: 12),
|
||||
ClideText(tc.missing.map((t) => '$t not found').join(' · '),
|
||||
fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusWarning),
|
||||
ClideText(' · ', muted: true, fontSize: clideFontSmall),
|
||||
_ThemeLink(tokens: tokens, kernel: kernel, themeName: themeName),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -423,8 +433,8 @@ class _ThemeLink extends StatelessWidget {
|
||||
builder: (ctx, hovered, _) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ClideText('theme: ', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
|
||||
ClideText(themeName, fontSize: 12, fontFamily: clideMonoFamily, color: hovered ? tokens.globalForeground : tokens.globalFocus),
|
||||
ClideText('theme: ', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily),
|
||||
ClideText(themeName, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: hovered ? tokens.globalForeground : tokens.globalFocus),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -489,9 +499,9 @@ class _OpenProjectDialogState extends State<_OpenProjectDialog> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ClideText('Open project', fontSize: 16, fontWeight: FontWeight.w600),
|
||||
const ClideText('Open project', fontSize: clideFontDialogTitle, fontWeight: FontWeight.w600),
|
||||
const SizedBox(height: 4),
|
||||
const ClideText('Enter the path to a git repository.', muted: true, fontSize: 13),
|
||||
const ClideText('Enter the path to a git repository.', muted: true, fontSize: clideFontMeta),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
@@ -503,7 +513,8 @@ class _OpenProjectDialogState extends State<_OpenProjectDialog> {
|
||||
child: EditableText(
|
||||
controller: _controller,
|
||||
focusNode: _focus,
|
||||
style: TextStyle(color: tokens.globalForeground, fontSize: 14, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback),
|
||||
style: TextStyle(
|
||||
color: tokens.globalForeground, fontSize: clideFontCaption, fontFamily: clideMonoFamily, fontFamilyFallback: clideMonoFamilyFallback),
|
||||
cursorColor: tokens.globalForeground,
|
||||
backgroundCursorColor: tokens.globalTextMuted,
|
||||
onSubmitted: (_) => unawaited(_submit()),
|
||||
@@ -511,7 +522,7 @@ class _OpenProjectDialogState extends State<_OpenProjectDialog> {
|
||||
),
|
||||
if (_error != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
ClideText(_error!, color: tokens.statusError, fontSize: 12),
|
||||
ClideText(_error!, color: tokens.statusError, fontSize: clideFontSmall),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
@@ -548,14 +559,14 @@ class _NotARepoDialog extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const ClideText('No git repo found', fontSize: 16, fontWeight: FontWeight.w600),
|
||||
const ClideText('No git repo found', fontSize: clideFontDialogTitle, fontWeight: FontWeight.w600),
|
||||
const SizedBox(height: 8),
|
||||
ClideText(path, muted: true, fontSize: 13),
|
||||
ClideText(path, muted: true, fontSize: clideFontMeta),
|
||||
const SizedBox(height: 8),
|
||||
const ClideText(
|
||||
'A clide project root requires a git repository.',
|
||||
muted: true,
|
||||
fontSize: 13,
|
||||
fontSize: clideFontMeta,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
|
||||
+5
-19
@@ -27,22 +27,8 @@ export 'src/ipc/schema_v1.dart';
|
||||
export 'src/panes/event_sink.dart';
|
||||
export 'src/panes/pane.dart' show Pane, PaneKind;
|
||||
|
||||
/// Build-time-stamped version string.
|
||||
///
|
||||
/// The Makefile's `build` target passes `--define=clideVersion=…`,
|
||||
/// stamping `pubspec.yaml`'s `version:` plus the git short SHA and
|
||||
/// dirty marker.
|
||||
const clideVersion = String.fromEnvironment(
|
||||
'clideVersion',
|
||||
defaultValue: '2.0.0-dev',
|
||||
);
|
||||
|
||||
const clideCommit = String.fromEnvironment(
|
||||
'clideCommit',
|
||||
defaultValue: 'unknown',
|
||||
);
|
||||
|
||||
const clideDate = String.fromEnvironment(
|
||||
'clideDate',
|
||||
defaultValue: 'unknown',
|
||||
);
|
||||
// clideVersion / clideCommit / clideDate live in lib/src/build_info.g.dart,
|
||||
// regenerated by every `make` build/run/test target from pubspec.yaml +
|
||||
// git short SHA + current UTC time. See `gen-build-info` in the
|
||||
// Makefile.
|
||||
export 'src/build_info.g.dart' show clideVersion, clideCommit, clideDate;
|
||||
|
||||
@@ -56,8 +56,14 @@ const String clideMonoFamily = 'JetBrainsMono';
|
||||
const double clideFontBody = 15;
|
||||
const double clideFontCaption = 14;
|
||||
const double clideFontMono = 14;
|
||||
const double clideFontMeta = 13;
|
||||
const double clideFontSmall = 12;
|
||||
const double clideFontBadge = 11;
|
||||
// Larger semantic sizes that aren't body text. clideFontDialogTitle is
|
||||
// the modal/dialog heading; clideFontWelcomeBanner is the oversized
|
||||
// "clide" mark on the welcome screen — one-off but worth naming.
|
||||
const double clideFontDialogTitle = 16;
|
||||
const double clideFontWelcomeBanner = 52;
|
||||
const double clideLineHeight = 1.25;
|
||||
|
||||
/// System fallback chain. Ordered by platform prevalence + quality of
|
||||
|
||||
Reference in New Issue
Block a user