feat: unified shell, diagnostics pack, native Office, sticky board
All checks were successful
CI / Windows build (push) Successful in 14m22s
All checks were successful
CI / Windows build (push) Successful in 14m22s
Make Surface remote debugging and classroom workflows viable: always-on structured logs with one-click zip export, a single AppShell chrome, OOXML PPTX/DOCX annotation without LibreOffice, and a first-class sticky board. Also drop spike/legacy ink widgets and tighten pen feel (predictor, PenInfoHistory, page-tile layer). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
99
lib/diagnostics/frame_sampler.dart
Normal file
99
lib/diagnostics/frame_sampler.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
// Frame / hitch sampler for diagnostic packs.
|
||||
|
||||
import 'badnote_log.dart';
|
||||
|
||||
class FrameSample {
|
||||
FrameSample({
|
||||
required this.at,
|
||||
required this.label,
|
||||
required this.ms,
|
||||
this.dropped = false,
|
||||
});
|
||||
|
||||
final DateTime at;
|
||||
final String label;
|
||||
final double ms;
|
||||
final bool dropped;
|
||||
|
||||
Map<String, Object?> toJson() => {
|
||||
'at': at.toIso8601String(),
|
||||
'label': label,
|
||||
'ms': ms,
|
||||
'dropped': dropped,
|
||||
};
|
||||
}
|
||||
|
||||
class FrameSampler {
|
||||
FrameSampler._();
|
||||
static final FrameSampler instance = FrameSampler._();
|
||||
|
||||
static const int capacity = 500;
|
||||
final List<FrameSample> _samples = <FrameSample>[];
|
||||
int overBudget = 0;
|
||||
int total = 0;
|
||||
|
||||
/// Budget for a single frame at 60fps.
|
||||
static const double budgetMs = 16.6;
|
||||
|
||||
void record(String label, double ms, {bool dropped = false}) {
|
||||
total++;
|
||||
final over = ms > budgetMs;
|
||||
if (over) overBudget++;
|
||||
final sample = FrameSample(
|
||||
at: DateTime.now(),
|
||||
label: label,
|
||||
ms: ms,
|
||||
dropped: dropped || over,
|
||||
);
|
||||
_samples.add(sample);
|
||||
if (_samples.length > capacity) {
|
||||
_samples.removeRange(0, _samples.length - capacity);
|
||||
}
|
||||
if (over || dropped) {
|
||||
BadNoteLog.instance.warn(
|
||||
LogSubsystem.frame,
|
||||
'slow_frame',
|
||||
fields: {'label': label, 'ms': ms, 'dropped': dropped},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void recordZoom({
|
||||
required double rawScale,
|
||||
required bool scaleDrop,
|
||||
required bool focalDrop,
|
||||
required double focalJumpPx,
|
||||
}) {
|
||||
record(
|
||||
'zoom',
|
||||
scaleDrop || focalDrop ? budgetMs + 1 : 8,
|
||||
dropped: scaleDrop || focalDrop,
|
||||
);
|
||||
BadNoteLog.instance.debug(
|
||||
LogSubsystem.frame,
|
||||
'zoom',
|
||||
fields: {
|
||||
'rawScale': rawScale,
|
||||
'scaleDrop': scaleDrop,
|
||||
'focalDrop': focalDrop,
|
||||
'focalJumpPx': focalJumpPx,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Map<String, Object?>> toJsonList() =>
|
||||
_samples.map((s) => s.toJson()).toList(growable: false);
|
||||
|
||||
Map<String, Object?> summary() => {
|
||||
'total': total,
|
||||
'overBudget': overBudget,
|
||||
'budgetMs': budgetMs,
|
||||
'recent': toJsonList(),
|
||||
};
|
||||
|
||||
void reset() {
|
||||
_samples.clear();
|
||||
overBudget = 0;
|
||||
total = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user