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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../diagnostics/badnote_log.dart';
|
||||
import '../../diagnostics/pen_event_ring.dart';
|
||||
import 'diagnostic_logger.dart';
|
||||
|
||||
/// Latest hardware pen state delivered by the native observer.
|
||||
@@ -169,6 +171,29 @@ class PenInputService {
|
||||
final key = '$rawPtr,$rawPen,$rawMask,$btnChange,${_current.tiltX},${_current.tiltY}';
|
||||
if (key != _lastPenLogKey) {
|
||||
_lastPenLogKey = key;
|
||||
PenEventRing.instance.recordHardware(
|
||||
barrel: _current.barrel,
|
||||
eraser: _current.eraser,
|
||||
inverted: _current.inverted,
|
||||
tiltX: _current.tiltX,
|
||||
tiltY: _current.tiltY,
|
||||
);
|
||||
BadNoteLog.instance.debug(
|
||||
LogSubsystem.penNative,
|
||||
'pen_hw',
|
||||
fields: {
|
||||
'ptrFlags': '0x${rawPtr.toRadixString(16)}',
|
||||
'penFlags': '0x${rawPen.toRadixString(16)}',
|
||||
'mask': '0x${rawMask.toRadixString(16)}',
|
||||
'btnChg': btnChange,
|
||||
'tiltX': _current.tiltX,
|
||||
'tiltY': _current.tiltY,
|
||||
'resolved': '0x${flags.toRadixString(16)}',
|
||||
'barrel': _current.barrel,
|
||||
'eraser': _current.eraser,
|
||||
'inverted': _current.inverted,
|
||||
},
|
||||
);
|
||||
DiagnosticLogger.instance.log(
|
||||
'PEN ptrFlags=0x${rawPtr.toRadixString(16)} '
|
||||
'penFlags=0x${rawPen.toRadixString(16)} '
|
||||
|
||||
Reference in New Issue
Block a user