feat: unified shell, diagnostics pack, native Office, sticky board
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:
2026-08-05 17:55:27 +08:00
parent 3cabc7e074
commit d346cc2670
49 changed files with 2883 additions and 2515 deletions

View File

@@ -0,0 +1,61 @@
// Double-buffer helper on top of [PageTileCache] to kill zoom white-flash:
// keep painting the last good tile while a higher-DPI raster is in flight.
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
import 'page_tile_cache.dart';
/// Holds the "last good" page image for the currently visible page so a zoom
/// settle never exposes an empty frame (plan W2 / R11).
class PageTileLayer extends ChangeNotifier {
PageTileLayer({PageTileCache? cache}) : _cache = cache ?? PageTileCache();
final PageTileCache _cache;
ui.Image? _lastGood;
TileKey? _lastKey;
PageTileCache get cache => _cache;
ui.Image? get lastGood => _lastGood;
TileKey? get lastKey => _lastKey;
/// Snap continuous zoom to a coarse DPI bucket (avoids a tile per frame).
static int dpiBucketFor(double zoom, {double baseDpi = 96, double step = 0.5}) {
final raw = zoom / step;
final snapped = raw.round().clamp(1, 16);
return (snapped * step * baseDpi).round();
}
/// Promote [image] as the last-good tile for [key].
void put(TileKey key, ui.Image image) {
_cache.put(key, image);
_lastGood = image;
_lastKey = key;
notifyListeners();
}
/// Prefer exact bucket; else fall back to last-good so zoom never blanks.
ui.Image? resolve(TileKey key) {
final hit = _cache.get(key);
if (hit != null) {
_lastGood = hit;
_lastKey = key;
return hit;
}
return _lastGood;
}
void clear() {
_lastGood = null;
_lastKey = null;
_cache.dispose();
notifyListeners();
}
@override
void dispose() {
clear();
super.dispose();
}
}