- Copy route: drop WordPress's GROUP BY when the flat row is the only join,
and read posts.ID tie-breaks as the flat post_id (own direction, once)
- Post status sync: index (post_status, post_date, post_id) replaces the
two-column idx_post_status_date (added / dropped by post-status-sync)
- dev36, 235k of 1.3M listings in a price range: main query 1.7 s -> 3 ms
- Verify: composer test (704), test:integration (597), phpcs, stan
- PR: PR-128
- on_transition() skips a recorded group whose table no longer exists and
removes it from tmdo_post_status_columns; every post status change used to
UPDATE the missing table and print a database error (seen on dev36 after a
throwaway group's table was dropped)
- Verify: PostStatusSyncTest (dropped table), integration 578, unit 701, phpcs, stan
- PR: PR-106
- TMDO_Post_Status_Sync: post group tables get post_status / post_date
columns (posts' collation) and an (post_status, post_date) index.
Brand-new empty tables get them on creation; existing tables only via
`wp tmdo post-status-sync [--dry-run] [--batch]`, never as DDL on a
populated table in a web request. Per-group state in
tmdo_post_status_columns ('columns' | 'ready').
- transition_post_status refreshes the copies; Hook Bus upserts that
create a post row fill them in (existing rows keep theirs).
- Bench (1M rows): add columns 0.05 s, index 5.3 s, batched backfill 32 s,
0 mismatches afterwards.
- Verify: core unit 659 / integration 466 green, phpcs clean
- PR: PR-028