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

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:
2026-05-18 13:22:55 +02:00
co-authored by Claude
parent 68aa34e9dd
commit 7c2eae42f1
12 changed files with 156 additions and 81 deletions
+3 -2
View File
@@ -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
+5
View File
@@ -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;
+17
View File
@@ -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);
+13
View File
@@ -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.
+29 -12
View File
@@ -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.
+4 -1
View File
@@ -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
-6
View File
@@ -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
+22 -3
View File
@@ -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();
});
}
+49 -38
View File
@@ -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,
children: [
ClideText('clide 2.0.0-dev', muted: true, fontSize: 12, fontFamily: clideMonoFamily),
ClideText(' · ', muted: true, fontSize: 12),
if (!tc.resolved)
ClideText('checking…', muted: true, fontSize: 12, fontFamily: clideMonoFamily)
else if (tc.allOk)
ClideText('application ok', fontSize: 12, 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),
_ThemeLink(tokens: tokens, kernel: kernel, themeName: themeName),
],
// 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 $clideVersion', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily),
ClideText(' · ', muted: true, fontSize: clideFontSmall),
if (!tc.resolved)
ClideText('checking…', muted: true, fontSize: clideFontSmall, fontFamily: clideMonoFamily)
else if (tc.allOk)
ClideText('application ok', fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: tokens.statusSuccess)
else
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
View File
@@ -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;
+6
View File
@@ -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