"""The T9 library bundle (goal 41 task 9): the project detail, rules, discoveries and chats pages on the Spry conversion — the project page's Details/Edit twin cards collapsed into ONE in-place editor (data-long-form) with the freed structure carrying the Goals reverse link and a create-task deep-link into the /tasks dialog, the rules page's Edit expander turned in-place with Delete demoted behind a ghost button and the new blast-radius attachments list, the discoveries rows' edit expander turned in-place, and breadcrumbs on the project and chat detail pages. Markup-level via http_get_page; the new slot members (goals vector, rule attachments, crumbs vectors) cross-checked against seeded database rows.""" from harness import * # noqa: F401,F403 — the shared wire helpers def run(ctx): auth, fake = ctx.auth, ctx.fake now = "strftime('%s','now')" print("== project detail: the in-place Details editor ==") # A dedicated project so the bundle owns every row it asserts on. state = slot_public(auth, "/projects", "projects") act(auth, state["create"], {"name": "t9-library", "git_url": "https://example.com/t9.git", "default_branch": "main", "summary": "the T9 probe project"}) rows = slot_public(auth, "/projects", "projects")["projects"] project_id = int([p["id"] for p in rows if p["name"] == "t9-library"][0]) detail = slot_public(auth, "/projects/%d" % project_id, "project") check(detail["update"] is not None, "the project slot authors the update action") page = http_get_page(auth, "/projects/%d" % project_id) check('data-long-form data-field="project"' in page, "the Details card is one data-long-form card") check(page.count('data-lf-edit') >= 1 and page.count('data-lf-view') >= 1, "the card carries the read/edit halves") check('data-lf-src="project.git_url"' in page and 'data-lf-src="project.summary"' in page, "the edit half prefills from the slot's current values") check("

Edit

" not in page, "the side-by-side Edit card is gone") check('stm-action="project.update"' in page, "the editor form posts the project update action") print("== project detail: breadcrumbs replace the back anchor ==") check("

All projects

" not in page, "the hand-drawn All-projects anchor is gone") check('stm-def-crumbs-as="project.crumbs"' in page, "the breadcrumb partial rides the project page") crumbs = detail["crumbs"] check([c["label"] for c in crumbs] == ["Dashboard", "Projects", "t9-library"] and crumbs[0]["href"] == "/" and crumbs[1]["href"] == "/projects" and crumbs[2].get("href") is None, "the crumbs vector trails Dashboard / Projects / (%s)" % [c["label"] for c in crumbs]) print("== project detail: the create-task deep-link ==") check("'#new?project=' + project.id" in page or "'/tasks#new?project=' + project.id" in page, "the create affordance deep-links the /tasks dialog prefill") check("New task in this project" in page, "the affordance names the project flow") tasks_page = http_get_page(auth, "/tasks") check("applyNewTaskPrefill" in tasks_page, "the tasks page ships the prefill pass") check("task-create-dialog" in tasks_page and "openAppDialog" in tasks_page, "the prefill opens T4's create dialog through the helper") print("== project detail: the goals reverse link ==") # Seed one primary goal and one co-developed link; a reference link # must NOT appear (context, not work happening here). conn = db() now = "strftime('%s','now')" conn.execute( "insert into goals (project_id, title, brief, branch_name, status," " priority, creator, created_at) values (?, 'T9 primary goal'," " 'built here', 'goal/t9-primary', 'active', 3, 'human', %s)" % now, (project_id,)) primary_goal = int(conn.execute("select max(id) from goals").fetchone()[0]) conn.execute( "insert into projects (name, git_url, default_branch, created_at)" " values ('t9-codev', 'https://example.com/t9c.git', 'main', %s)" % now) codev_project = int(conn.execute("select max(id) from projects") .fetchone()[0]) conn.execute( "insert into goals (project_id, title, brief, branch_name, status," " priority, creator, created_at) values (?, 'T9 co-dev goal'," " 'built alongside', 'goal/t9-codedev', 'planning', 3, 'human', %s)" % now, (codev_project,)) codev_goal = int(conn.execute("select max(id) from goals").fetchone()[0]) conn.execute( "insert into goal_links (goal_id, project_id, relationship)" " values (?, ?, 'co-developed')", (codev_goal, project_id)) conn.execute( "insert into goals (project_id, title, brief, branch_name, status," " priority, creator, created_at) values (?, 'T9 ref goal'," " 'read only', 'goal/t9-ref', 'draft', 3, 'human', %s)" % now, (codev_project,)) ref_goal = int(conn.execute("select max(id) from goals").fetchone()[0]) conn.execute( "insert into goal_links (goal_id, project_id, relationship)" " values (?, ?, 'reference')", (ref_goal, project_id)) conn.commit() conn.close() detail = slot_public(auth, "/projects/%d" % project_id, "project") goals = {str(g["id"]): g for g in detail["goals"]} check(set(goals) == {str(primary_goal), str(codev_goal)}, "the goals vector carries the primary and co-developed goals only" " (got %s)" % sorted(goals)) check(goals[str(primary_goal)]["relationship"] == "primary" and goals[str(codev_goal)]["relationship"] == "co-developed", "each goal row names its relationship") check(goals[str(primary_goal)]["display"] == "active" and goals[str(codev_goal)]["display"] == "planning", "the rows carry the human display status") page = http_get_page(auth, "/projects/%d" % project_id) check("Goals in this project" in page and "'/goals/' + goal.id" in page, "the page renders the goals reverse-link table") check("'badge badge-' + goal.status" in page, "goal rows badge their status like the goals list") print("== rules: the in-place editor + the demoted delete ==") rules_state = slot_public(auth, "/rules", "rules") act(auth, rules_state["create"], { "name": "T9 blast rule", "body": "The blast radius probe rule."}) conn = db() rule_id = int(conn.execute( "select id from rules where name = 'T9 blast rule'").fetchone()[0]) # Attach it to the probe project and to one task. conn.execute( "insert into rule_projects (rule_id, project_id, created_at)" " values (?, ?, %s)" % now, (rule_id, project_id)) conn.execute( "insert into tasks (project_id, title, status, priority, created_at)" " values (?, 'T9 blast task', 'queued', 5, %s)" % now, (project_id,)) blast_task = int(conn.execute("select max(id) from tasks").fetchone()[0]) conn.execute( "insert into task_rules (rule_id, task_id, created_at)" " values (?, ?, %s)" % now, (rule_id, blast_task)) conn.commit() conn.close() rules_state = slot_public(auth, "/rules", "rules") row = [r for r in rules_state["rules"] if r["name"] == "T9 blast rule"][0] check([(a["kind"], a["label"]) for a in row["attachments"]] == [("project", "t9-library"), ("task", "T9 blast task")], "the rule row lists its blast radius in attachment order (%s)" % [(a["kind"], a["label"]) for a in row["attachments"]]) check(all(int(a["id"]) > 0 for a in row["attachments"]), "each attachment carries the linked row's id") unattached = [r for r in rules_state["rules"] if len(r.get("attachments") or []) == 0] check(len(unattached) >= 1 or len(rules_state["rules"]) == 1, "rules attached nowhere list no attachments (no crash)") page = http_get_page(auth, "/rules") check("New rule" not in page and "rule-create-dialog" in page and "openAppDialog('rule-create-dialog')" in page, "the create expander became a dialog behind a button") check("Edit" not in page, "the per-rule Edit expander is gone") check('data-long-form data-field="rule-row"' in page and 'data-lf-toggle' in page, "each rule edits through the in-place engine") # The compiled page entity-encodes the body text ("…" -> …); # assert on the escaped form and on the ghost class itself. check("Delete rule…" in page and 'class="btn-ghost btn-sm"' in page, "delete is demoted to a ghost button out of the row's face") check('stm-text="attachment.label"' in page, "the blast radius renders as links on the rule card") print("== discoveries: the in-place editor ==") page = http_get_page(auth, "/discoveries") check("Edit" not in page, "the discoveries Edit expander is gone") check('data-long-form data-field="discovery-row"' in page, "each discovery edits through the in-place engine") check('stm-text="discovery.body"' in page and 'name="body"' in page, "the edit textarea prefills from the rendered row value") check('discoveryRowVisible' in page and 'setDiscoveryQuery' in page, "the client-side search semantics are unchanged") state = slot_public(auth, "/discoveries", "discoveries") check("query" in state and "more" in state, "the slot still seeds the search box and the more count") print("== chats: breadcrumbs on the detail page ==") # Seed the chat row directly (the slot builder reads any owned row; # this bundle does not restart the app with a scripted LLM - the # chatui bundle owns that surface). conn = db() conn.execute( "insert into advisor_chats (user_id, title, status, created_at," " updated_at) values ((select id from spry_users where username =" " 'admin'), 'T9 crumbs probe', 'idle', %s, %s)" % (now, now)) chat_id = int(conn.execute("select max(id) from advisor_chats") .fetchone()[0]) conn.commit() conn.close() chat_uri = "/chats/%d" % chat_id chat_state = slot_public(auth, chat_uri, "chat") check(chat_state["id"] == chat_id, "the probe chat exists (%s)" % chat_uri) crumbs = chat_state["crumbs"] check([c["label"] for c in crumbs] == ["Dashboard", "Chats", "T9 crumbs probe"] and crumbs[0]["href"] == "/" and crumbs[1]["href"] == "/chats" and crumbs[2].get("href") is None, "the chat slot trails Dashboard / Chats / (%s)" % [c["label"] for c in crumbs]) page = http_get_page(auth, chat_uri) check('stm-def-crumbs-as="chat.crumbs"' in page, "the breadcrumb partial rides the chat detail page") check("chat-thread" in page and "chat-composer" in page, "the conversation still leads with the composer below") detail = slot_public(auth, "/chats/%d" % chat_id, "chat") check(detail["delete"] is not None, "the seeded chat still authors its delete action") print("== chats list: the demoted delete ==") page = http_get_page(auth, "/chats") check('class="btn-ghost btn-sm" stm-action="chat.delete"' in page, "the list's delete is a quiet ghost button") check("alert alert-warning" in page, "the unconfigured-advisor notice uses the Spry alert")