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:
@@ -1,90 +1,38 @@
|
||||
// lib/editor/input/diagnostic_logger.dart
|
||||
//
|
||||
// On-device input diagnostics. When the user manually enables the diagnostic
|
||||
// (the toolbar toggle), every native pen event (raw button/flag/tilt fields)
|
||||
// and every zoom frame is emitted through the standard `dart:developer` log
|
||||
// channel (name 'badnote.input') — capturable via `flutter run`, DevTools, or
|
||||
// any log tool — AND mirrored to a text file as a fallback for the packaged
|
||||
// GUI build, which has no attached console. Disabled by default (no overhead).
|
||||
// Compatibility facade over [BadNoteLog] + [PenEventRing]. The PDF editor's
|
||||
// toolbar toggle still calls start/stop; globally, [BadNoteLog.start] runs at
|
||||
// app launch so packaged builds always have a session file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import '../../diagnostics/badnote_log.dart';
|
||||
|
||||
class DiagnosticLogger {
|
||||
DiagnosticLogger._();
|
||||
static final DiagnosticLogger instance = DiagnosticLogger._();
|
||||
|
||||
final List<String> _buffer = <String>[];
|
||||
File? _file;
|
||||
Timer? _timer;
|
||||
int _epochMs = 0;
|
||||
bool _verbose = false;
|
||||
bool get isActive => _verbose || BadNoteLog.instance.path != null;
|
||||
|
||||
bool _active = false;
|
||||
bool get isActive => _active;
|
||||
/// Absolute path of the structured session log (preferred), else null.
|
||||
String? get path => BadNoteLog.instance.path;
|
||||
|
||||
/// Absolute path of the current log file (shown in the overlay), or null.
|
||||
String? path;
|
||||
|
||||
/// Begin a session. Enables the `dart:developer` log channel immediately and
|
||||
/// opens the fallback file (best-effort). Safe to call repeatedly.
|
||||
/// Begin a verbose input session (also ensures the global log is running).
|
||||
Future<void> start() async {
|
||||
if (_active) return;
|
||||
_active = true; // developer.log works even if the file can't be opened
|
||||
_epochMs = DateTime.now().millisecondsSinceEpoch;
|
||||
developer.log('--- session start ${DateTime.now().toIso8601String()} ---',
|
||||
name: 'badnote.input');
|
||||
try {
|
||||
Directory dir;
|
||||
try {
|
||||
dir = await getApplicationDocumentsDirectory();
|
||||
} catch (_) {
|
||||
dir = await getTemporaryDirectory();
|
||||
}
|
||||
final file = File('${dir.path}${Platform.pathSeparator}badnote_input_log.txt');
|
||||
await file.writeAsString(
|
||||
'# BadNote input diagnostic log\n'
|
||||
'# started ${DateTime.now().toIso8601String()}\n'
|
||||
'# columns: <ms> <kind> <fields...>\n',
|
||||
flush: true,
|
||||
);
|
||||
_file = file;
|
||||
path = file.path;
|
||||
_buffer.clear();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (_) => _flush());
|
||||
} catch (_) {
|
||||
// File is a fallback; never break the app over it.
|
||||
}
|
||||
_verbose = true;
|
||||
await BadNoteLog.instance.start();
|
||||
BadNoteLog.instance.info(LogSubsystem.diag, 'verbose_input_on');
|
||||
}
|
||||
|
||||
/// Emit one diagnostic line through the standard log channel and the file.
|
||||
void log(String line) {
|
||||
if (!_active) return;
|
||||
developer.log(line, name: 'badnote.input');
|
||||
if (_file == null) return;
|
||||
final t = DateTime.now().millisecondsSinceEpoch - _epochMs;
|
||||
_buffer.add('$t $line');
|
||||
if (_buffer.length >= 1000) _flush();
|
||||
BadNoteLog.instance.debug(LogSubsystem.penNative, line);
|
||||
}
|
||||
|
||||
Future<void> _flush() async {
|
||||
final file = _file;
|
||||
if (file == null || _buffer.isEmpty) return;
|
||||
final chunk = '${_buffer.join('\n')}\n';
|
||||
_buffer.clear();
|
||||
try {
|
||||
await file.writeAsString(chunk, mode: FileMode.append, flush: true);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/// Flush and stop. The file remains on disk for retrieval.
|
||||
Future<void> stop() async {
|
||||
if (!_active) return;
|
||||
_active = false;
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
await _flush();
|
||||
if (!_verbose) return;
|
||||
_verbose = false;
|
||||
BadNoteLog.instance.info(LogSubsystem.diag, 'verbose_input_off');
|
||||
await BadNoteLog.instance.flush();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user