models/ — iso() in models/base; every to_dict uses it; Rulebook/Topic/Rule on TimestampMixin, token/usage models on CreatedAtMixin.
mcp/ — rulebooks.rules_payload replaces five copies of the six-key rules block; dead resolve_bearer_to_user_id removed.
routes/ — supersession.attach_relations is the one seam for both doors; PUT+PATCH note update merged; get_current_user_id / not_found / SECRET_MASK / _project_scope replace per-blueprint copies.
Second pass over the tests/ ledger after bbee0d0. fake_record(**attrs) is the
one MagicMock-with-real-attributes builder (to_dict mirrors them; the
note-2109 hazard documented once); fake_note/fake_task/fake_snippet/
fake_project/fake_milestone/fake_system/fake_rulebook/fake_topic/fake_rule
carry each model's ordinary defaults on top of it, replacing 14 per-file
factories (two rulebook trios in tool-vs-service wordings, _fake_task, _fake_ms,
_fake_project, _plan_note, _fake_snippet, two _snippet adapters now one-liners
over fake_snippet). FakeMCP replaces the five closure-over-a-list registrar
fakes (+ _Recorder); loc() and design_token_stub() replace the paired _loc /
_token / _T stand-ins; every hand-built async_session mock (9 helper defs and
14 inline copies) now starts from make_mock_session(). Call sites rewritten by
AST with each file's former defaults made explicit, so behaviour is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading all 28 models against each other: 54 `x.isoformat() if x else None` /
`x.isoformat()` expressions in 23 to_dict methods, in two guarded/unguarded
wordings, become iso() from models/base.py — uniform, and a row read before
flush serialises as null instead of raising. Rulebook / RulebookTopic / Rule
carried byte-identical copies of TimestampMixin's two columns;
InvitationToken / PasswordResetToken / NoteUsageEvent carried CreatedAtMixin's —
all six now use the mixin. AppLog and RetrievalLog keep their explicit
created_at, commented: their composite index orders on `created_at.desc()`,
which needs the column object in the class body. Schema-neutral (same column
definitions) — no migration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading the 16 tool modules against each other: the six-key applicable-rules
block (applicable_rules, applicable_rules_truncated, subscribed_rulebooks,
project_rules, suppressed_rules, suppressed_topics) was hand-built in five
places — enter_project, get_project, get_task (legacy plans), get_milestone
(three of the six) and services/planning.start_planning. rulebooks_svc.
rules_payload() is now the one place that names them; get_milestone gains the
three it lacked, so every rules-carrying payload reads the same. list_rules /
list_always_on_rules share _rule_summary. mcp/auth.resolve_bearer_to_user_id
duplicated resolve_bearer's parsing and had no product caller (only its own
tests) — removed; the tests now exercise resolve_bearer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reading the 28 route modules against each other and against the MCP tools:
- routes/notes.py carried a PUT and a PATCH handler that were the same
function minus the supersedes contract on one of them — one handler now
serves both verbs, so both carry it.
- The two _attach_supersession copies (REST + MCP) become
supersession_svc.attach_relations(uid, note_id, data, hint=) — the seam
the two surfaces must agree through; only the agent surface adds the
one-sentence reading hint.
- Three local _uid() wrappers over g.user.id → scribe.auth.get_current_user_id
like every other module; design_systems' private _not_found → routes.utils.
not_found; the four "********" literals → settings_svc.SECRET_MASK with the
read/write contract written once.
- routes/plugin.py: the project_id/repo resolution block and the
comma-separated id parse were copied into three endpoints — _project_scope()
and _int_list() now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- background.start_periodic(interval, work, label=) replaces the three hand-rolled
while-True/sleep/try loops in logging, auth and notifications.
- services/scheduler.ScheduledJob replaces the four private BackgroundScheduler
copies in recurrence/version_pinning/trash/db_maintenance schedulers; public
start_/stop_/reschedule_ surfaces unchanged.
- api_keys.hash_token is the one sha256 helper; auth.py used to inline it 5x.
- auth.is_registration_open reads via settings.get_admin_setting; notification
prefs read via settings.get_setting; _fire_share_email uses _get_user_email.
- projects.get_project_summary / milestones.get_project_milestone_summary are
now the one-id view of their batch siblings instead of a second copy of the
queries; sharing.best_permission_by (was _deduplicate_by_permission) is the
one rank-dedup, now also used by list_projects_for_user.
- backup: the row builders for every section both exporters carry are named
functions, so a column added to one export cannot silently miss the other.
- iso() from models.base replaces the attr.isoformat()-if-attr-else-None idiom
and db_maintenance._iso across services; backup keeps its explicit shape.
- trash.py hoists the sql_delete/timedelta imports it re-imported per function.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- assets/auth-shared.css: the five auth views carried byte-identical scoped
copies of the page/card/brand/footer/field/input/error rules (~60 lines
each); they now load one stylesheet the way the editors load
editor-shared.css. .closed-msg/.error-block/.success-msg (identical bodies)
are one .auth-note; the form rules are scoped under .auth-card so nothing
leaks into the rest of the app.
- api/client.apiErrorMessage(e, fallback): the one place the {"error"} envelope
is unpacked; replaces ten six-line `"body" in e` catch blocks.
- utils/dateFormat: fmtDate / fmtStamp / fmtLogStamp replace eight local
formatDate/formatTime copies (three byte-identical pairs); the file’s old
Calendar/Home helpers had no callers and are gone. useRelativeTime gains
relativeTimeOrDate for the two workspace panels’ identical variant.
- components.css now owns the .modal-* shape (overlay/card/title/message/
actions/btn/primary/danger). It was copied into four views and lived in
editor-shared.css, which ConfirmDialog — styleless, teleported to <body> —
silently depended on: opened from SnippetDetailView before any editor view
had loaded, it rendered unstyled. Views keep only their own overrides.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The full-read shape audit of Scribe (milestone #296), one consolidation pass per area, each landed on dev with CI green:
tests/helpers.py(make_mock_session, ensure_user, fake_record + per-model fake_*, FakeMCP, loc, design_token_stub) and opt-in conftest fixtures (_bind_user,_dispose_engine,_no_supersession); 25 test files rewritten onto them.iso()in models/base; every to_dict uses it; Rulebook/Topic/Rule on TimestampMixin, token/usage models on CreatedAtMixin.rulebooks.rules_payloadreplaces five copies of the six-key rules block; deadresolve_bearer_to_user_idremoved.supersession.attach_relationsis the one seam for both doors; PUT+PATCH note update merged;get_current_user_id/not_found/SECRET_MASK/_project_scopereplace per-blueprint copies.background.start_periodic,scheduler.ScheduledJob(four scheduler modules rewritten),api_keys.hash_token, single→batch delegation for project/milestone summaries,sharing.best_permission_by, backup row builders,iso()sweep.assets/auth-shared.css,api/client.apiErrorMessage,utils/dateFormatfmtDate/fmtStamp/fmtLogStamp +relativeTimeOrDate, modal canon in components.css (fixes ConfirmDialog rendering unstyled before an editor view loaded — issue #2856).Canon recorded as snippets #2833–#2859; ledger classification follows this merge (the project-2 binding reads main).
🤖 Generated with Claude Code
- assets/auth-shared.css: the five auth views carried byte-identical scoped copies of the page/card/brand/footer/field/input/error rules (~60 lines each); they now load one stylesheet the way the editors load editor-shared.css. .closed-msg/.error-block/.success-msg (identical bodies) are one .auth-note; the form rules are scoped under .auth-card so nothing leaks into the rest of the app. - api/client.apiErrorMessage(e, fallback): the one place the {"error"} envelope is unpacked; replaces ten six-line `"body" in e` catch blocks. - utils/dateFormat: fmtDate / fmtStamp / fmtLogStamp replace eight local formatDate/formatTime copies (three byte-identical pairs); the file’s old Calendar/Home helpers had no callers and are gone. useRelativeTime gains relativeTimeOrDate for the two workspace panels’ identical variant. - components.css now owns the .modal-* shape (overlay/card/title/message/ actions/btn/primary/danger). It was copied into four views and lived in editor-shared.css, which ConfirmDialog — styleless, teleported to <body> — silently depended on: opened from SnippetDetailView before any editor view had loaded, it rendered unstyled. Views keep only their own overrides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>