fix(watch): drop proposed_folder_auto; a recorded folder is never re-decided

The extra bookkeeping field showed up in Obsidian's Properties panel as a
second 'proposed folder' row, which was confusing.

Simpler rule replaces it: once proposed_folder is set it is never changed.
The owner edits it if they disagree; deleting the field asks for a fresh
suggestion.
This commit is contained in:
sam
2026-09-26 08:19:00 +10:00
parent 92ea55f77d
commit 4f92d3782b
2 changed files with 27 additions and 30 deletions

View File

@@ -34,12 +34,12 @@ from .vault import Vault
from .writer import FIELD_ORDER, Writer
# Fields the watcher owns. Never present on a filed note.
ADMIN_FIELDS = ("proposed_folder", "proposed_folder_auto", "proposed_toc",
"proposed_tags", "confidence", "approved", "proposed_for")
ADMIN_FIELDS = ("proposed_folder", "proposed_toc", "proposed_tags",
"confidence", "approved", "proposed_for")
PROPOSAL_ORDER = (
"title", "type", "status",
"proposed_folder", "proposed_folder_auto", "proposed_toc", "proposed_tags",
"proposed_folder", "proposed_toc", "proposed_tags",
"confidence", "approved", "proposed_for",
"created", "updated", "tags", "aliases", "id",
)
@@ -79,19 +79,16 @@ def proposal_is_stale(note: Note) -> bool:
return recorded != content_hash(note)
def user_selected_folder(note: Note) -> str | None:
def existing_folder(note: Note) -> str | None:
"""
The folder the owner chose by hand, if they changed it.
The folder already recorded on a note, if any — chosen by the owner or by
an earlier proposal.
We keep the watcher's own suggestion in `proposed_folder_auto`, so a
difference between the two means a human edited it. Their choice wins —
the watcher must never overwrite it.
Once set, this is never changed by the watcher. The owner can edit it, or
delete the field to ask for a fresh suggestion.
"""
chosen = str(note.fm.get("proposed_folder") or "").strip()
auto = str(note.fm.get("proposed_folder_auto") or "").strip()
if chosen and auto and chosen != auto:
return chosen
return None
return chosen or None
class Watcher:
@@ -133,12 +130,13 @@ class Watcher:
# ----------------------------------------------------------------- proposing
def _write_proposal(self, note: Note, proposal: Proposal,
preserve_folder: str | None = None) -> list[str]:
keep_folder: str | None = None) -> list[str]:
"""Add the proposal fields to the note in place. Returns log lines."""
fm = dict(note.fm)
# The owner's hand-picked folder always wins over a fresh suggestion.
effective = preserve_folder or proposal.target_folder
# A folder that is already recorded is never re-decided. The owner's
# choice wins, and so does an earlier proposal.
effective = keep_folder or proposal.target_folder
# Resolve the real index + section so the proposal reads clearly.
toc_rel, toc_section, _ = self.writer.resolve_toc(
@@ -152,7 +150,6 @@ class Watcher:
fm["proposed_toc"] = "(not indexed)"
fm["proposed_folder"] = effective
fm["proposed_folder_auto"] = proposal.target_folder
if proposal.proposed_tags:
fm["proposed_tags"] = proposal.proposed_tags
fm["confidence"] = round(proposal.confidence, 2)
@@ -164,8 +161,8 @@ class Watcher:
self.writer._atomic_write(Path(note.path), text)
note_msg = f"**proposed** `{note.filename}` → `{effective}` "
if preserve_folder:
note_msg += "(your folder kept) "
if keep_folder:
note_msg += "(folder already set — kept) "
note_msg += f"(confidence {proposal.confidence:.2f})"
return [note_msg] + [f" - {w}" for w in proposal.warnings]
@@ -263,12 +260,12 @@ class Watcher:
f"**held** `{note.filename}` — "
+ "; ".join(proposal.warnings)
)
elif not proposal.target_folder and not user_selected_folder(note):
elif not proposal.target_folder and not existing_folder(note):
actions.append(f"**held** `{note.filename}` — no destination determined")
else:
actions += self._write_proposal(
note, proposal,
preserve_folder=user_selected_folder(note),
keep_folder=existing_folder(note),
)
except Exception as e: # never let one note kill the loop
actions.append(f"**error** `{note.filename}`: {type(e).__name__}: {e}")