mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 19:27:01 +01:00
Audited the template against 514 real Claude sessions (1503 top-level prompts) joined to the 49 notes in BACKLOG.md. Sections were judged on whether the agent actually read them, not on fill rate — four of them said "leave blank if none" in their own helper, so a low fill rate was the section working correctly. Measured over the 22 note/session pairs: estimate filled 22, referenced 1 (4%) done filled 22, quoted back 4 (18%) refs filled 14, URLs fetched 5 (35%) tech the agent opened pubspec.yaml/CLAUDE.md anyway in 54% ask 100% ask rate with it, 92% without — no measurable effect Kept: title, what, where, must, done. Dropped: tech (into `where`), ask, nice and never (into `must` as `optional:` / `must not:` lines), depends, estimate. Renamed `refs` -> `read first`. Added `verify`, the only addition: "test it yourself, on the phone" was the single most repeated correction in the corpus, and 3 of the 7 filled `never` sections were already smuggling it in. Fixes a latent data-loss bug found while testing this. parse() treats an unknown `## heading` as content on purpose (values may contain subheadings), so a legacy `## tech` block was folded into the preceding section's value AND the note still reported conforming — opening any old note in the stepper would have swallowed it on the next save. NoteTemplate.retiredLabels now makes those notes explicitly non-conforming, so they open raw and untouched. tool/migrate_backlog.dart converts an old export (export -> migrate -> import). It is idempotent and gated: it throws if a rewritten note does not conform or if any source line vanishes. All 49 notes migrated on the desktop and merged to the phone (0 new, 25 updated, 24 unchanged). Also formats the few files dart format had never been run over, and extracts a local in frame_stats_io.dart so the reflowed line stays under 80 chars. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
2.9 KiB
Dart
90 lines
2.9 KiB
Dart
// coverage:ignore-file
|
|
// TEMPORARY diagnostic instrument, web variant. See frame_stats_io.dart.
|
|
//
|
|
// Armed at build time (`--dart-define=TODO_FRAME_STATS=1`) rather than by an
|
|
// environment variable, which a browser does not have. Reports to the devtools
|
|
// console.
|
|
import 'dart:async';
|
|
|
|
import 'dart:ui' show FramePhase;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
|
|
/// Whether the frame-stats instrument is armed for this build.
|
|
///
|
|
/// Read as a string rather than `bool.fromEnvironment`, which only accepts the
|
|
/// exact literal `true` — `--dart-define=TODO_FRAME_STATS=1` silently yields
|
|
/// false and the instrument produces nothing at all.
|
|
const String _frameStatsFlag = String.fromEnvironment('TODO_FRAME_STATS');
|
|
|
|
/// Whether the instrument is armed (`--dart-define=TODO_FRAME_STATS=1`).
|
|
const bool frameStatsEnabled =
|
|
_frameStatsFlag == '1' || _frameStatsFlag == 'true';
|
|
|
|
const _slowFrameMs = 8;
|
|
const _probePeriod = Duration(milliseconds: 16);
|
|
const _blockedMs = 24;
|
|
|
|
final Stopwatch _clock = Stopwatch();
|
|
Timer? _probe;
|
|
final List<int> _cadenceUs = <int>[];
|
|
|
|
String get _at => (_clock.elapsedMilliseconds / 1000).toStringAsFixed(2);
|
|
|
|
/// Installs the frame-timing and event-loop instruments.
|
|
void installFrameStats() {
|
|
if (!frameStatsEnabled) return;
|
|
_clock.start();
|
|
debugPrint('[frame-stats web] armed');
|
|
|
|
SchedulerBinding.instance.addTimingsCallback((timings) {
|
|
for (final t in timings) {
|
|
final total =
|
|
t.buildDuration.inMilliseconds + t.rasterDuration.inMilliseconds;
|
|
if (total > _slowFrameMs) {
|
|
debugPrint(
|
|
'[SLOW-FRAME $_at s] total=${total}ms '
|
|
'(build=${t.buildDuration.inMilliseconds} '
|
|
'raster=${t.rasterDuration.inMilliseconds})',
|
|
);
|
|
}
|
|
// Cadence is the headline number for the >=100fps done-condition.
|
|
final vsync = t.timestampInMicroseconds(FramePhase.vsyncStart);
|
|
_cadenceUs.add(vsync);
|
|
}
|
|
_reportCadence();
|
|
});
|
|
|
|
var expected = _clock.elapsedMicroseconds + _probePeriod.inMicroseconds;
|
|
_probe = Timer.periodic(_probePeriod, (_) {
|
|
final now = _clock.elapsedMicroseconds;
|
|
final lateMs = (now - expected) / 1000;
|
|
if (lateMs > _blockedMs) {
|
|
debugPrint('[UI-BLOCKED $_at s] ${lateMs.toStringAsFixed(1)}ms');
|
|
}
|
|
expected = now + _probePeriod.inMicroseconds;
|
|
});
|
|
}
|
|
|
|
void _reportCadence() {
|
|
if (_cadenceUs.length < 120) return;
|
|
final deltas = <int>[];
|
|
for (var i = 1; i < _cadenceUs.length; i++) {
|
|
deltas.add(_cadenceUs[i] - _cadenceUs[i - 1]);
|
|
}
|
|
deltas.sort();
|
|
final p50 = deltas[deltas.length ~/ 2] / 1000;
|
|
final p95 = deltas[(deltas.length * 95) ~/ 100] / 1000;
|
|
debugPrint(
|
|
'[frame-stats web] cadence p50=${p50.toStringAsFixed(2)}ms '
|
|
'p95=${p95.toStringAsFixed(2)}ms (~${(1000 / p50).toStringAsFixed(1)} fps)',
|
|
);
|
|
_cadenceUs.clear();
|
|
}
|
|
|
|
/// Stops the probe.
|
|
void disposeFrameStats() {
|
|
_probe?.cancel();
|
|
}
|