|
@@ -348,10 +348,9 @@ def run(ctx):
|
|
|
and page.index("<h3>Progress</h3>") < page.index("status-head"),
|
|
and page.index("<h3>Progress</h3>") < page.index("status-head"),
|
|
|
"the progress feed renders above the status card")
|
|
"the progress feed renders above the status card")
|
|
|
check(page.index("grid-2") < page.index("status-head")
|
|
check(page.index("grid-2") < page.index("status-head")
|
|
|
- and page.index("status-head") < page.index("<h3>Mandate</h3>"),
|
|
|
|
|
|
|
+ and page.index("status-head") < page.index('data-long-form data-field="task"'),
|
|
|
"status and description sit side by side inside the grid (status first)")
|
|
"status and description sit side by side inside the grid (status first)")
|
|
|
- check("<h3>Mandate</h3>" in page
|
|
|
|
|
- and "plan-expander" not in page,
|
|
|
|
|
|
|
+ check("Mandate" in page and "plan-expander" not in page,
|
|
|
"the task page carries no plan expander (plans live on the goal)")
|
|
"the task page carries no plan expander (plans live on the goal)")
|
|
|
# Answered blocks fold behind the history expander (this fixture's
|
|
# Answered blocks fold behind the history expander (this fixture's
|
|
|
# three blocks are all answered by now).
|
|
# three blocks are all answered by now).
|
|
@@ -775,10 +774,13 @@ def run(ctx):
|
|
|
act(auth, task_state(auth, keeper)["update"], {"priority": "2"})
|
|
act(auth, task_state(auth, keeper)["update"], {"priority": "2"})
|
|
|
check("Keep this mandate intact." == task_state(auth, keeper)["description"],
|
|
check("Keep this mandate intact." == task_state(auth, keeper)["description"],
|
|
|
"a priority-only edit leaves the mandate untouched")
|
|
"a priority-only edit leaves the mandate untouched")
|
|
|
- # And the full edit form prefills the mandate (carried fix).
|
|
|
|
|
|
|
+ # Task 12: the mandate's edit surface is the in-place card
|
|
|
|
|
+ # editor (the expander no longer carries the field); the editor
|
|
|
|
|
+ # prefills from state, not stm-attribute.value (a content
|
|
|
|
|
+ # attribute, which textareas ignore).
|
|
|
task_page_html = http_get_page(auth, "/tasks/%d" % keeper)
|
|
task_page_html = http_get_page(auth, "/tasks/%d" % keeper)
|
|
|
- check('stm-attribute.value="task.description"' in task_page_html,
|
|
|
|
|
- "the edit expander pre-populates the mandate textarea")
|
|
|
|
|
|
|
+ check('data-lf-src="task.description"' in task_page_html,
|
|
|
|
|
+ "the mandate card editor prefills the textarea from state")
|
|
|
|
|
|
|
|
print("== plan 13 B4: comments display as footnotes, body-first ==")
|
|
print("== plan 13 B4: comments display as footnotes, body-first ==")
|
|
|
# A quoted agent comment on the keeper task drives the display
|
|
# A quoted agent comment on the keeper task drives the display
|
|
@@ -846,3 +848,82 @@ def run(ctx):
|
|
|
if c["body"].startswith("Order probe")]
|
|
if c["body"].startswith("Order probe")]
|
|
|
check(len(bodies) == 2 and bodies[0] == "Order probe new remark",
|
|
check(len(bodies) == 2 and bodies[0] == "Order probe new remark",
|
|
|
"the task comment thread leads with the newest remark")
|
|
"the task comment thread leads with the newest remark")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ # =====================================================================
|
|
|
|
|
+ # Task 12 (task half): the mandate-card in-place editor.
|
|
|
|
|
+ # =====================================================================
|
|
|
|
|
+ print("== task 12: the mandate card editor ==")
|
|
|
|
|
+ act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
|
|
|
|
|
+ "project_id": "1", "title": "Mandate editor task", "priority": "4",
|
|
|
|
|
+ "description": "# The original mandate\n\nWith a body."})
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ mandate_task = conn.execute(
|
|
|
|
|
+ "select id from tasks where title = 'Mandate editor task'"
|
|
|
|
|
+ ).fetchone()[0]
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+
|
|
|
|
|
+ detail = task_state(auth, mandate_task)
|
|
|
|
|
+ check(detail.get("editable_all") is True and detail.get("draft") is True,
|
|
|
|
|
+ "the mandate editor task is a draft (every field editable)")
|
|
|
|
|
+ task_page = http_get_page(auth, "/tasks/%d" % mandate_task)
|
|
|
|
|
+ card_at = task_page.index('data-long-form data-field="task"')
|
|
|
|
|
+ edit_at = task_page.index('data-lf-toggle>Edit</button>', card_at)
|
|
|
|
|
+ area_at = task_page.index('data-lf-src="task.description"', card_at)
|
|
|
|
|
+ check(card_at >= 0 and edit_at > card_at and area_at > card_at,
|
|
|
|
|
+ "the mandate card carries the in-place Edit button and editor")
|
|
|
|
|
+ check('name="description" rows="8"' in task_page,
|
|
|
|
|
+ "the editor's textarea is the wide in-place one")
|
|
|
|
|
+ # The expander no longer carries the mandate field (short
|
|
|
|
|
+ # properties only) — and no other description textarea exists.
|
|
|
|
|
+ expander_at = task_page.index('class="edit-expander"')
|
|
|
|
|
+ expander_end = task_page.index('</details>', expander_at)
|
|
|
|
|
+ expander_html = task_page[expander_at:expander_end]
|
|
|
|
|
+ check('name="description"' not in expander_html,
|
|
|
|
|
+ "the edit expander no longer carries the mandate field")
|
|
|
|
|
+
|
|
|
|
|
+ # The PREFILL contract: the editor reads task.description from
|
|
|
|
|
+ # state at open (JS-applied — stm-attribute.value is a no-op on
|
|
|
|
|
+ # textarea). The compiled page therefore carries the state path,
|
|
|
|
|
+ # and the save is a description-only update.
|
|
|
|
|
+ check(task_page.count('data-lf-src="task.description"') == 1,
|
|
|
|
|
+ "exactly one mandate editor textarea exists, keyed to state")
|
|
|
|
|
+ act(auth, detail["update"], {"task_id": str(mandate_task),
|
|
|
|
|
+ "description": "# The edited mandate\n\nSecond."})
|
|
|
|
|
+ saved = task_state(auth, mandate_task)
|
|
|
|
|
+ check(saved["description"] == "# The edited mandate\n\nSecond."
|
|
|
|
|
+ and saved["title"] == "Mandate editor task",
|
|
|
|
|
+ "the description-only save round-trips and leaves the title alone")
|
|
|
|
|
+
|
|
|
|
|
+ # Editability rules unchanged: an ACTIVE task keeps the read-only
|
|
|
|
|
+ # mandate (no Edit button, no editor textarea) while priority
|
|
|
|
|
+ # remains editable in the expander.
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ conn.execute("insert into tasks (project_id, title, status, priority,"
|
|
|
|
|
+ " created_at) values (1, 'Active mandate task', 'working', 5,"
|
|
|
|
|
+ " strftime('%s','now'))")
|
|
|
|
|
+ conn.commit()
|
|
|
|
|
+ active_task = conn.execute(
|
|
|
|
|
+ "select id from tasks where title = 'Active mandate task'"
|
|
|
|
|
+ ).fetchone()[0]
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ active = task_state(auth, active_task)
|
|
|
|
|
+ check(active.get("editable_all") is False
|
|
|
|
|
+ and active.get("editable_priority") is True,
|
|
|
|
|
+ "an active task edits priority only")
|
|
|
|
|
+ # The compiled page is state-independent markup: statum gates the
|
|
|
|
|
+ # editor through stm-if, which HIDES (never removes) the branch.
|
|
|
|
|
+ # What the raw page can promise: the Edit button and the editor
|
|
|
|
|
+ # form are BOTH inside task.editable_all-gated branches (statum
|
|
|
|
|
+ # hides them at render), while the rendered mandate view carries
|
|
|
|
|
+ # no gate at all.
|
|
|
|
|
+ active_page = http_get_page(auth, "/tasks/%d" % active_task)
|
|
|
|
|
+ card_at = active_page.find('data-long-form data-field="task"')
|
|
|
|
|
+ card_end = active_page.find('</section>', card_at)
|
|
|
|
|
+ card_html = active_page[card_at:card_end]
|
|
|
|
|
+ toggle_at = card_html.find('data-lf-toggle')
|
|
|
|
|
+ editor_at = card_html.find('data-lf-edit')
|
|
|
|
|
+ check(toggle_at < 0 or 'stm-if="task.editable_all"' in card_html,
|
|
|
|
|
+ "an active task's Edit button is draft-gated (stm-if hides it)")
|
|
|
|
|
+ check(editor_at < 0 or card_html.rfind('stm-if="task.editable_all"', 0, editor_at) > 0,
|
|
|
|
|
+ "an active task's editor form sits inside the draft-gated branch")
|