← All Pull Requests

Remind bridge callers to checkpoint edits and close completed scratch documents #6

Closed Draft opened by John Lauer 2026-09-07

Fusion routing demos can leave modified, never-saved scratch documents open even after native source export. CRR accumulated eight such boards and Fusion reported a recovery-file creation failure. Add read-only documentHygiene state and actionable save/cleanup hints after add-in commands (except activity banners), preserving existing result/error fields. Distinguish isSaved from isModified, exempt pristine blank documents, and report unknown properties rather than assuming clean state.

Hints require milestone checkpoints, artifact verification, and exact task-owned document cleanup. No automatic save, discard, ownership inference, recovery deletion, or cloud write is introduced. Update the no-modal skill with the same lifecycle discipline.

Validation: six unit tests pass. Surgically installed and tested on CRR without restart; live dirty-document response reports one pending save, clean response reports zero. Eight task boards checkpointed to native archives, ZIP integrity and embedded board XML checked, then closed. Final board reopened via embedded BRD and confirmed 576 segments, 89 vias, 12 pours, zero airwires; verification tab then closed. Direct Document.Open of the native FBRD was not supported in this session. The underlying recovery write failure is not diagnosed by this change.

Diff Skip to comments (1)

--- a/addin/AdomBridge/commands/__init__.py+++ b/addin/AdomBridge/commands/__init__.py@@ -1,157 +1,162 @@⋯ 153 unchanged lines ⋯             "error": f"Unknown add-in command: {command}",             "_hint": f"Valid add-in commands: {', '.join(sorted(COMMAND_HANDLERS.keys()))}.",         }-    return handler(app, args)+    result = handler(app, args)+    if command != "activity_banner":+        from .document_hygiene import attach_document_hygiene+        result = attach_document_hygiene(app, result)+    return result+--- a/addin/AdomBridge/commands/document_hygiene.py+++ b/addin/AdomBridge/commands/document_hygiene.py@@ -0,0 +1,61 @@+"""Read-only save reminders. Called on Fusion's main thread after command completion.++Exports are checkpoints, not proof that Fusion cleared its modified/recovery state.+This module never saves, closes, infers ownership from names, or deletes recovery data.+"""+++def _property(obj, name):+    try:+        return getattr(obj, name)+    except Exception:+        return None+++def inspect_documents(app):+    documents = []+    try:+        for i in range(app.documents.count):+            doc = app.documents.item(i)+            saved = _property(doc, 'isSaved')+            modified = _property(doc, 'isModified')+            documents.append({+                'name': _property(doc, 'name'),+                'isSaved': saved, 'isModified': modified,+                'needsSave': modified is True or (saved is False and modified is not False),+                'saveStateKnown': saved is not None and modified is not None,+            })+    except Exception as exc:+        return {'inspectionComplete': False, 'documents': documents, 'error': str(exc)}+    pending = [d for d in documents if d['needsSave']]+    return {'inspectionComplete': True, 'openDocumentCount': len(documents),+            'needsSaveCount': len(pending), 'documents': documents,+            'recoveryHealth': 'notInspected', 'ownership': 'notInferred'}+++def attach_document_hygiene(app, result):+    if not isinstance(result, dict):+        return result+    state = inspect_documents(app)+    result['documentHygiene'] = state+    count = state.get('needsSaveCount', 0)+    if count or state.get('openDocumentCount', 0) > 3:+        names = [d['name'] for d in state['documents'] if d['needsSave']]+        hint = (+            'Document checkpoint: %s open, %s need saving%s. '+            'Save edited work at milestones and before ending the task. A source export '+            'or Document.CopyToDesktop is a separate checkpoint and may leave Fusion '+            'isSaved=false/isModified=true; verify the artifact and re-read save state. '+            'Close only this task\'s completed scratch documents by exact identity with '+            'fusion_close_document after verifying a current native checkpoint; never '+            'bulk-discard other work. If Fusion reports recovery-file failure, checkpoint '+            'immediately, then save or close that document; do not just dismiss recovery. '+            'Use a task subfolder when saving to cloud.'+        ) % (state.get('openDocumentCount', '?'), count,+             (' (' + ', '.join(str(n) for n in names[:6]) + (', ...' if len(names)>6 else '') + ')') if names else '')+        result['_hintDocumentHygiene'] = hint+        result['_hint'] = (result.get('_hint', '') + ' ' + hint).strip()+    elif not state['inspectionComplete']:+        result['_hintDocumentHygiene'] = 'Save-state inspection incomplete; inspect documents before cleanup.'+    return result+--- a/skills/fusion-no-modal-dialogs/SKILL.md+++ b/skills/fusion-no-modal-dialogs/SKILL.md@@ -1,180 +1,205 @@⋯ 177 unchanged lines ⋯ 4. Is there a dismissal sweep doing load-bearing work? If yes, that is the bug. 5. Does any guard swallow its own failure? Make it shout. 6. Test by recording the commands that actually reach Fusion.++## Save checkpoints and close completed scratch documents++After edits, inspect `documentHygiene` in bridge responses. `isSaved` and `isModified`+are separate: a previously saved document can have new edits; a pristine blank Untitled+is not a dirty board. Unknown properties are not proof of a clean state.++At each completed routing/DRC milestone and before ending a task:++1. Save the working document, or export a fresh native checkpoint to a durable task folder.+2. Verify the file exists, has content, and can be reopened; log its path and hash with the+   exact document identity. Exporting/`Document.CopyToDesktop` may leave the live tab+   `isSaved=false, isModified=true`; do not report that as a Fusion save.+3. Close completed documents opened by this task, one exact identity at a time, after+   checkpoint verification. Do not infer ownership from a name prefix, close all tabs,+   delete recovery data, or discard edits in a pre-existing user document.+4. Re-enumerate documents. Leave only ongoing work and pre-existing user documents;+   Fusion may retain one pristine Untitled tab.++If Fusion reports that recovery-file creation failed, checkpoint immediately and then+save or close the affected task document. The warning alone does not establish why+recovery failed; inspect disk/path/permissions if it recurs in a clean session. Never+silence the warning by disabling recovery. The advisory response does not inspect+recovery health and does not automatically save or close documents.+--- a/tests/test_document_hygiene.py+++ b/tests/test_document_hygiene.py@@ -0,0 +1,29 @@+import importlib.util+from pathlib import Path+from types import SimpleNamespace as NS+import unittest+spec=importlib.util.spec_from_file_location('hygiene',Path(__file__).parents[1]/'addin/AdomBridge/commands/document_hygiene.py')+m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)+def app(*docs): return NS(documents=NS(count=len(docs),item=lambda i:docs[i]))+def doc(saved,modified,name='board'): return NS(name=name,isSaved=saved,isModified=modified)+class Hygiene(unittest.TestCase):+ def test_export_does_not_clear_dirty_state(self):+  r=m.attach_document_hygiene(app(doc(False,True)),{'success':True,'_hint':'Existing hint'})+  self.assertEqual(r['documentHygiene']['needsSaveCount'],1)+  self.assertTrue(r['_hint'].startswith('Existing hint'))+  self.assertIn('separate checkpoint',r['_hint'])+ def test_previously_saved_but_modified(self):+  self.assertEqual(m.inspect_documents(app(doc(True,True)))['needsSaveCount'],1)+ def test_blank_untouched_is_not_dirty(self):+  self.assertEqual(m.inspect_documents(app(doc(False,False,'Untitled')))['needsSaveCount'],0)+ def test_unknown_is_not_reported_clean(self):+  r=m.inspect_documents(app(NS(name='unsupported')))+  self.assertFalse(r['documents'][0]['saveStateKnown'])+ def test_errors_and_original_hint_preserved(self):+  r=m.attach_document_hygiene(app(doc(False,True)),{'success':False,'error':'actual failure','_hint':'original'})+  self.assertFalse(r['success']);self.assertEqual(r['error'],'actual failure')+ def test_enumeration_failure_does_not_replace_result(self):+  r=m.attach_document_hygiene(NS(),{'success':True})+  self.assertTrue(r['success']);self.assertFalse(r['documentHygiene']['inspectionComplete'])+if __name__=='__main__':unittest.main()+

Comments

John Lauer · 2mo ago

Superseded by branch PR #7, which merges the whole fix/document-save-hints branch (this file set plus the routing branch it sits on) as a true merge commit after a three-way reconciliation with master 1.9.370. Nothing from this PR is lost: document_hygiene.py, the dispatch hook, the no-modal skill text and the unit tests are all in #7.

Log in to comment.