0.1.107 — sidebar pin diagnostic dedupe + richer payload + Restore-Inbox refreshState fix
Follow-up to a user report that 0.1.105's defensive Inbox pinning
still wasn't enough: the user's pin_diagnostics log showed a
sustained total=1 pinned=0 warning across ~120 rebuilds, AND
tapping "Restore default Inbox" appeared to do nothing. Two
diagnostic upgrades + one real-fix land here.
1. Diagnostic now identifies the unpinned row
The 0.1.105 emit was honest but useless on its own: total=1 pinned=0 told us the row failed both the is_default == true and
title.toLowerCase() == "inbox" checks, but it did not tell us
what the row actually was. So the user's only debugging move was
"tap Restore" and re-paste a wall of warnings.
In editor_app/lib/core/build_helpers.dart,
emitSidebarPinDiagnostics now describes up to the first 5 rows
inline:
total=1 pinned=0 signedIn=true. Rows: #42 "Notes" plain/cloud/drag.
Each row tuple has shape:
#<id> "<title>" <default|plain>/<local|cloud>/<pin|drag>
so a single line tells the operator the row's id, exact title
(quoted to surface stray whitespace or casing), default flag,
which list it came from (_localCourses vs _courses), and
whether isCategoryPinned matched it. Plus signedIn= so we know
whether we're in the local-only or signed-in _allCategories
branch.
2. Diagnostic deduped per composition change
Before: emitted on every sidebar rebuild — a busy editor logs ~120 lines/second of identical warnings while the user is just typing in the editor. The Debug Log card became unusable.
Constraint: extension methods can't declare instance fields, so
the cache had to live on _AppShellState. New private field
_lastSidebarPinDiagnosticKey parks the previous emit's
(total/pinned/signedIn/rows-summary) key. The extension method
reads + writes it directly, which is legal because Dart extensions
can access existing fields on the underlying type.
Net effect: one log line per composition change (add/remove a category, flag flip, cloud sync replacing a row, sign-in/out). Editing notes no longer drowns the log.
3. Restore Default Inbox now triggers refreshState
Symptom (user-reported): tapping "Restore default Inbox" produced the success snackbar but the sidebar did not update.
Root cause in editor_app/lib/core/maintenance_actions.dart,
_restoreLocalStarterTemplate: it called
_seedStarterInboxAlongsideExisting() which mutates
_localCourses / _selectedCourse directly (extension methods
cannot call setState). The seeder relies on its caller to
trigger a rebuild — _loadLocalState does, but
_restoreLocalStarterTemplate did not. Without it, the new
local Inbox sat in _localCourses until any unrelated rebuild
flushed it through.
Fix: explicit if (mounted) refreshState(); immediately after the
await _seedStarterInboxAlongsideExisting(); call.
Verification
flutter analyzeclean acrosseditor_appfor the three modified files (app_shell.dart,core/build_helpers.dart,core/maintenance_actions.dart). Only pre-existing info-level lints (prefer_single_quotes,use_string_in_part_of_directives) remain — none new.- Diagnostic dedupe is purely additive: the warning still fires
the first time
pinned == 0 && total > 0is observed; it re-fires whenever the row tuple changes; it does not fire between identical rebuilds. So we lose nothing diagnostic, just spam. - The
refreshState()fix is identical in shape to the otherif (mounted) refreshState();calls already sprinkled throughmaintenance_actions.dart(see_clearLocalData,_emptyDeletedNotes,_pullCloudNotesToLocal).
Operator runbook
- Reproduce the original report:
- Open the editor, open Settings → Debug log, filter source by
sidebar.pin_diagnostics. - Confirm exactly one debug-level breadcrumb on first paint (no more spam).
- If you see a
pinned=0 total>0warning, paste the line — theRows: ...segment now tells us exactly what category is blocking the pin.
- Open the editor, open Settings → Debug log, filter source by
- Tap Settings → Restore default Inbox → confirm the sidebar
immediately gains a pinned "Inbox" row and the diagnostic
re-emits with
pinned≥1 total≥1.