[ { "id": "T001", "description": "Migration: comment_threads table created with all columns, PK (tenant, thread_id), CHECK exactly-one of ticket_id/project_task_id, expected FKs", "implemented": true, "featureIds": ["F001"] }, { "id": "T002", "description": "Migration: indexes exist on comment_threads — (tenant, ticket_id, last_activity_at DESC), (tenant, project_task_id, last_activity_at DESC), partial on email_message_id", "implemented": true, "featureIds": ["F002"] }, { "id": "T003", "description": "Migration: comments has nullable thread_id, parent_comment_id, deleted_at columns post-migration with correct FKs", "implemented": true, "featureIds": ["F003"] }, { "id": "T004", "description": "Migration: project_task_comments has nullable thread_id, parent_comment_id, deleted_at columns post-migration with correct FKs", "implemented": true, "featureIds": ["F004"] }, { "id": "T005", "description": "Backfill: every pre-existing comments row has thread_id populated; comment_threads has one row per such comment with root_comment_id=comment_id, reply_count=0", "implemented": true, "featureIds": ["F005"] }, { "id": "T006", "description": "Backfill: comment_threads.email_message_id is set from comment.metadata.email.messageId when present; NULL otherwise", "implemented": true, "featureIds": ["F005"] }, { "id": "T007", "description": "Backfill: running migration twice does not duplicate threads (idempotent)", "implemented": true, "featureIds": ["F005", "F006"] }, { "id": "T008", "description": "Backfill: every pre-existing project_task_comments row has thread_id populated; analogous thread row exists", "implemented": true, "featureIds": ["F006"] }, { "id": "T009", "description": "Migration: post-NOT-NULL enforcement, attempting INSERT with NULL thread_id is rejected for both tables", "implemented": true, "featureIds": ["F007"] }, { "id": "T010", "description": "Migration: email_sending_logs has new comment_thread_id column post-migration", "implemented": true, "featureIds": ["F008"] }, { "id": "T011", "description": "Types: ICommentThread compiles and exports all expected fields", "implemented": true, "featureIds": ["F009"] }, { "id": "T012", "description": "Types: IComment has thread_id, parent_comment_id, deleted_at as expected types", "implemented": true, "featureIds": ["F010"] }, { "id": "T013", "description": "Types: IProjectTaskComment has thread_id, parent_comment_id, deleted_at as expected types", "implemented": true, "featureIds": ["F011"] }, { "id": "T014", "description": "Model: createComment with parent_comment_id=NULL creates a new comment_threads row + comment whose thread_id matches; root_comment_id equals new comment_id", "implemented": true, "featureIds": ["F012"] }, { "id": "T015", "description": "Model: createComment with parent_comment_id inherits parent's thread_id; new comment.parent_comment_id matches", "implemented": true, "featureIds": ["F013"] }, { "id": "T016", "description": "Model: createComment rejects when parent_comment_id refers to a comment on a DIFFERENT ticket", "implemented": true, "featureIds": ["F013"] }, { "id": "T017", "description": "Model: createComment rejects when parent_comment_id refers to a soft-deleted parent", "implemented": true, "featureIds": ["F013"] }, { "id": "T018", "description": "Model: createComment rejects internal reply on a client-visible thread root (and vice versa)", "implemented": true, "featureIds": ["F013", "F024"] }, { "id": "T019", "description": "Model: inserting a reply increments comment_threads.reply_count by 1 and bumps last_activity_at", "implemented": true, "featureIds": ["F014"] }, { "id": "T020", "description": "Model: deleting a leaf comment hard-deletes the row and decrements reply_count", "implemented": true, "featureIds": ["F015"] }, { "id": "T021", "description": "Model: deleting a root comment that has children soft-deletes (deleted_at set, note replaced with '[deleted]'); children unaffected", "implemented": true, "featureIds": ["F015"] }, { "id": "T022", "description": "Model: findCommentsByTicketId returns thread_id, parent_comment_id, deleted_at for every comment including soft-deleted", "implemented": true, "featureIds": ["F016"] }, { "id": "T023", "description": "Model: createTaskComment with NULL parent creates new comment_threads row with project_task_id set", "implemented": true, "featureIds": ["F017"] }, { "id": "T024", "description": "Model: createTaskComment with parent inherits thread_id and increments counters", "implemented": true, "featureIds": ["F018", "F019"] }, { "id": "T025", "description": "Model: deleteTaskComment leaf vs root-with-children behavior matches tickets", "implemented": true, "featureIds": ["F020"] }, { "id": "T026", "description": "Model: getTaskComments returns thread_id, parent_comment_id, deleted_at", "implemented": true, "featureIds": ["F021"] }, { "id": "T027", "description": "Action: commentActions.createComment accepts parent_comment_id in payload and forwards to model", "implemented": true, "featureIds": ["F022"] }, { "id": "T028", "description": "Action: TICKET_COMMENT_ADDED event payload includes thread_id, parent_comment_id, is_reply boolean", "implemented": true, "featureIds": ["F023"] }, { "id": "T029", "description": "Action: client-user attempting to create internal reply is rejected with 403/permission error", "implemented": true, "featureIds": ["F024"] }, { "id": "T030", "description": "Action: projectTaskCommentActions.createTaskComment accepts parent_comment_id and forwards to model", "implemented": true, "featureIds": ["F025"] }, { "id": "T031", "description": "Action: TASK_COMMENT_ADDED event payload includes thread_id, parent_comment_id, is_reply", "implemented": true, "featureIds": ["F026"] }, { "id": "T032", "description": "Action: assertOwnCommentOrInternalUser allows client to delete own reply; rejects deleting another client's reply", "implemented": true, "featureIds": ["F027"] }, { "id": "T033", "description": "Email inbound: reply token in body resolves to a comment_id; resulting comment lands in same thread with parent=latest comment in that thread", "implemented": true, "featureIds": ["F028", "F033"] }, { "id": "T034", "description": "Email inbound: In-Reply-To matching email_sending_logs.rfc_message_id resolves to that comment's thread", "implemented": true, "featureIds": ["F029", "F033"] }, { "id": "T035", "description": "Email inbound: References[] chain walked end-to-start; first match wins; resolves to expected thread", "implemented": true, "featureIds": ["F030", "F033"] }, { "id": "T036", "description": "Email inbound: provider thread id exact match on comment_threads.email_provider_thread_id resolves to that thread", "implemented": true, "featureIds": ["F031", "F033"] }, { "id": "T037", "description": "Email inbound: when no resolution strategy matches but ticket matches (subject heuristic / email_metadata), a new top-level thread is created on that ticket", "implemented": true, "featureIds": ["F032"] }, { "id": "T038", "description": "Email inbound: resolution precedence — reply token wins over In-Reply-To when both present", "implemented": true, "featureIds": ["F028", "F029"] }, { "id": "T039", "description": "Email outbound: top-level send generates a fresh RFC Message-ID and writes it to comment_threads.email_message_id; outgoing email has no In-Reply-To header", "implemented": true, "featureIds": ["F034"] }, { "id": "T040", "description": "Email outbound: reply in existing thread sets In-Reply-To to the latest outbound rfc_message_id stored in email_sending_logs for that comment_thread_id", "implemented": true, "featureIds": ["F035"] }, { "id": "T041", "description": "Email outbound: References header is comment_threads.email_references + previous Message-ID appended; new entry persisted into the column", "implemented": true, "featureIds": ["F036"] }, { "id": "T042", "description": "Email outbound: a row is created in email_reply_tokens with comment_id of the new outbound comment; recipient_email matches", "implemented": true, "featureIds": ["F037"] }, { "id": "T043", "description": "Email outbound: email_sending_logs row has comment_thread_id, rfc_message_id, reply_token_hash, provider_message_id populated", "implemented": true, "featureIds": ["F038"] }, { "id": "T044", "description": "Email round-trip: outbound + simulated inbound with matching In-Reply-To lands the reply back in the originating thread", "implemented": true, "featureIds": ["F029", "F034", "F035"] }, { "id": "T045", "description": "UI: CommentThreadList renders one HybridThreadGroup per thread sorted by last_activity_at; respects newestFirst toggle", "implemented": true, "featureIds": ["F039", "F049"] }, { "id": "T046", "description": "UI: HybridThreadNode renders child threads recursively; each level past 0 marked as sub-thread", "implemented": true, "featureIds": ["F040"] }, { "id": "T047", "description": "UI: clicking Collapse hides children and switches bar to show Expand + Open in drawer", "implemented": true, "featureIds": ["F041"] }, { "id": "T048", "description": "UI: clicking Open in drawer opens the drawer with the right root + replies", "implemented": true, "featureIds": ["F042", "F044"] }, { "id": "T049", "description": "UI: InlineReplyComposer shows Mark-as-Internal switch only; no Mark-as-Resolution; is_internal default matches parent's is_internal", "implemented": true, "featureIds": ["F043"] }, { "id": "T050", "description": "UI: Drawer composer submits create with parent_comment_id; drawer closes; inline list shows new reply after close", "implemented": true, "featureIds": ["F044", "F053"] }, { "id": "T051", "description": "UI: depth-4 CSS cap — indent does not grow past depth 4 even with depth-5 data", "implemented": true, "featureIds": ["F059"] }, { "id": "T052", "description": "UI: sub-thread bar (depth >= 1) uses dashed border + white background; depth-0 uses solid grey background", "implemented": true, "featureIds": ["F060"] }, { "id": "T053", "description": "UI: Reply button only visible on hover; keyboard-accessible focus also reveals it (.c-actions opacity rules)", "implemented": true, "featureIds": ["F046", "F051"] }, { "id": "T054", "description": "UI: soft-deleted comments render '[deleted]' placeholder with reduced opacity; no Reply button", "implemented": true, "featureIds": ["F058"] }, { "id": "T055", "description": "UI: tab counts at thread level — All counts threads, Client = threads whose root non-internal, Internal = threads whose root internal, Resolution = threads with any is_resolution=true comment", "implemented": true, "featureIds": ["F048"] }, { "id": "T056", "description": "UI: ticket thread sort order toggle re-sorts threads by last_activity_at; within-thread replies still chronological regardless of toggle", "implemented": true, "featureIds": ["F049", "F061"] }, { "id": "T057", "description": "Playwright e2e (ticket): hover comment → Reply button appears → click → inline composer opens", "implemented": true, "featureIds": ["F046", "F051"] }, { "id": "T058", "description": "Playwright e2e (ticket): submit inline reply → indented child renders under parent with thread bar showing 1 reply", "implemented": true, "featureIds": ["F052"] }, { "id": "T059", "description": "Playwright e2e (ticket): collapse a thread → children hide → bar shows Expand + Open in drawer; click Expand to restore", "implemented": true, "featureIds": ["F041"] }, { "id": "T060", "description": "Playwright e2e (ticket): click Open in drawer → drawer opens with root + replies; close returns inline view unchanged", "implemented": true, "featureIds": ["F042", "F044"] }, { "id": "T061", "description": "Playwright e2e (ticket): reply inside drawer adds the comment; closing drawer shows the new reply inline", "implemented": true, "featureIds": ["F053"] }, { "id": "T062", "description": "Playwright e2e (ticket): reply to a reply spawns a sub-thread bar with dashed border", "implemented": true, "featureIds": ["F040", "F060"] }, { "id": "T063", "description": "Playwright e2e (ticket): tab switch to Internal hides client-rooted threads; counts update accordingly", "implemented": true, "featureIds": ["F048"] }, { "id": "T064", "description": "Playwright e2e (project task): hover comment → Reply button → inline composer → submit → child indents", "implemented": true, "featureIds": ["F054", "F055", "F056"] }, { "id": "T065", "description": "Playwright e2e (project task): collapse + Open in drawer behavior identical to ticket flow", "implemented": true, "featureIds": ["F057"] }, { "id": "T066", "description": "Playwright e2e (project task): is_internal toggle absent from task reply composer (tasks have no internal/client split)", "implemented": true, "featureIds": ["F056"] }, { "id": "T067", "description": "Regression: existing ticket with only legacy comments renders identically to today — no thread bars, no indent, layout unchanged", "implemented": true, "featureIds": ["F005", "F039"] }, { "id": "T068", "description": "Regression: existing project task with legacy comments renders unchanged", "implemented": true, "featureIds": ["F006", "F055"] }, { "id": "T069", "description": "Regression: editing a legacy comment still works; updated_at advanced; tree unaffected", "implemented": true, "featureIds": ["F005"] }, { "id": "T070", "description": "Regression: reactions on comments continue to attach via commentReactionActions; reactions allowed on replies too", "implemented": true, "featureIds": ["F046"] }, { "id": "T071", "description": "Performance: opening a ticket with 100 threads x 5 replies each renders within current SLA budget (existing perf test threshold)", "implemented": true, "featureIds": ["F002", "F039"] }, { "id": "T072", "description": "DB integrity: deleting a ticket cascades to comment_threads via FK (and to comments via existing FK)", "implemented": true, "featureIds": ["F001"] }, { "id": "T073", "description": "DB integrity: deleting a project_task cascades to comment_threads via FK", "implemented": true, "featureIds": ["F001"] }, { "id": "T074", "description": "DB integrity: CHECK (exactly-one of ticket_id/project_task_id) prevents inserting a thread with both NULL or both set", "implemented": true, "featureIds": ["F001"] }, { "id": "T075", "description": "Email idempotency: receiving the same provider message twice does not duplicate the comment (existing email_processed_messages dedupe still effective with new thread routing)", "implemented": true, "featureIds": ["F028", "F029", "F033"] }, { "id": "T076", "description": "Email: outbound to multiple recipients issues distinct reply_tokens per recipient as today", "implemented": true, "featureIds": ["F037"] }, { "id": "T077", "description": "Concurrency: two replies created in parallel on the same thread both succeed; reply_count increments to +2; no lost update", "implemented": true, "featureIds": ["F014"] }, { "id": "T078", "description": "RBAC: client user can reply to a client-visible thread on their own ticket; cannot reply on internal thread; cannot reply on a ticket they don't have access to", "implemented": true, "featureIds": ["F024"] }, { "id": "T079", "description": "Drawer focus management: opening the drawer moves focus into the drawer; closing returns focus to the originating thread bar (matches Radix Dialog focus behavior)", "implemented": true, "featureIds": ["F044"] }, { "id": "T080", "description": "UI: 'Add Comment' top-level submit always creates a new thread (parent_comment_id absent) — never accidentally lands inside the most recent thread", "implemented": true, "featureIds": ["F012", "F022"] } ]