The sample set carries photography for CP‑0102 only — these are Memopryl’s files standing in.
MR-200 … MR-239 · requirements docs/requirement-inputs/RI-listings.md (RI-270 … RI-410)
Nothing listed every listing on a channel. MR-015 put a Channel listings section on the product record — every listing that references one product. This is the transpose: every listing on one account, whatever product it is built from. Same table, the other axis, and both are needed — one answers what have we built for Memopryl, the other answers what is live on eBay right now.
MR-027 is the second reason. It declared that channel-listing rows carry quantity · date listed · created by · sold · views, and that those columns retire the team’s tracking sheet. Nothing had ever drawn them. Either they appear on a grid somebody actually opens, or the tracking sheet outlives us.
The tabs are not cosmetic and the grids do not aggregate. eBay’s row is a listing — one pack size or one combo, one SKU, one price. Shopify’s row is a product that holds three variants, each with its own SKU and price. An aggregate view would have to pick one grain and lie about the other channel, so it does not exist — Paul’s call, and the right one.
What is shared is the machinery, not the columns: the list sheet is ported whole from sales-channel-accounts-list.html — the COLS model with on / extra / pin / nosort, the picker modal, aria-sort, and drag-to-resize with double-click restore. MR-080 said any third list screen should take all four from there. This is the third — and the first to change the model rather than copy it.
Columns wrap, and wrapping is a property of the column (MR-207). A column holding one unbreakable token — a SKU, an id, a date, a number — stays on one line and ellipsises: breaking MEMP-1-OB-EBY across two lines makes it unreadable and unfindable by eye. A column holding prose wraps, because ellipsising a listing title hides the half that tells two listings apart. Nine columns are marked wrap; the rest are not.
The column headers lock while the rows scroll (MR-208). That needed the grid to become its own scroll region, not a tall table on a scrolling page: overflow-x:auto already made the table wrapper the header’s scrollport, but with no height cap it never scrolled vertically, so a sticky header had nothing to stick to. The card’s bottom is now pinned to the viewport and the rows scroll inside it, with the banner, the deck and the tab bar staying put above. The height is measured, not a calc(100vh − 320px) constant — the banner above is conditional and the deck wraps at narrow widths, so a constant is wrong the first time it matters.
Two caps stop wrapping degrading into one character per line. The drag floor is now 96px, not 64 — below roughly that width ordinary words stop breaking between words and start breaking through them. And a wrapped cell is clamped to three lines, so one runaway title cannot make a six-line row and destroy the scan; the full value stays in the cell’s title, so nothing is lost, only folded.
By product is the default: one row per Shopify product, expanding to its variants. Every variant is flat — one row per purchasable unit, pinned on the SKU, with a Product column beside it. The toggle sits next to Show and exists only on this tab; eBay’s grain never changes, so it has no toggle to offer.
Grouped answers is this product configured — three variants read as one thing you set up once, and 2 of 3 is the compact form of a failure that has no other symptom. Flat answers what am I selling, at what price, and can it be bought: a scan across every unit in the store, sortable by price or by purchasability across products. Grouping makes that impossible, because those numbers live one level down inside a sub-table and you cannot sort on them.
The Product column is what makes the flat view work, and it is the reason the flat shape was first rejected and then adopted. The original objection (RI-273) was that status and published are product facts with nowhere to live on a variant row. They live in the inheritance the Product column makes visible: with it, repeating them reads as this belongs to that product rather than as a claim about this one variant. Product-only rows (RI-274) stayed rejected — they hide the SKU and the price, which are the two facts this screen was asked for first.
The flat rows are derived, never fixtured twice (RI-287). Two lists for one truth is how a grid comes to disagree with itself, and a variant missing from one of them would vanish silently — the same failure shape the screen exists to catch.
The attention count changes with the view, and both numbers are right: three products need you, or five variants do. The grain moved, not the data.
A Shopify product must be ACTIVE before CheckoutChamp will sync it, but it need not be published to the Online Store (RI-246). So the whole configure sequence happens off-storefront and publishing is the last act, not the first. Avinol PM is the row that says so: active, syncing, buyable nowhere. A single merged column would have drawn it as broken.
The column names its channel, and that is deliberate (MR-241). It was labelled Published until 2026-09-07, which was wrong in a way only a measurement caught: on this store an ACTIVE product is auto-published to Meta and Microsoft Copilot within seconds, and no API call takes it back off — publishableUnpublish returns userErrors: [] and changes nothing (MR-199, RI-268). So a bare Published: no was false: the product is on two catalogues the whole time. Naming the channel makes the column true without adding a second signal for channels this business takes no orders from. Those two channels are tabled, not resolved (Paul, 2026-09-07) — if they ever matter, this column is where the second signal goes.
The column does not ask Shopify whether the product is fine. It reads the CheckoutChamp campaign-product id out of the Shopify metafield (RI-247 / RI-248) — the only evidence the sync ran for that variant. MEMP-6-OB-VD has none, which is why Memopryl reads 2 of 3. That row is not invented for the mockup: the SKU Map fixture has carried it as ☐ pending since MR-127.
Views is blank on every ended row, and that is correct. eBay reports it while a listing is running and stops when it ends — it is a live read, not a stored fact, which is why SAMPLE-DATA has always said L-198 … views —. Nothing lost it.
CA-03 is deliberately active and connection lost. Its three rows are not wrong — they are frozen, because nothing has refreshed them since the token expired at 08:02. Sold and Views are the two that go quietly wrong. That is a fact about the grid rather than about any one row, so it is a banner above the grid and a greyed row treatment, not a per-cell warning.
There is no listing-record screen and this round does not add one. A drawer answers the whole question — identity, lineage, bookkeeping, the job that made it, the way out to eBay or Shopify — without a screen that would mostly repeat the grid. If a listing ever grows editable fields, that is the moment it earns a record.
An icon on the pinned cell, beside the SKU or the product name, opens the thing itself — ebay.com/itm/<item> or the storefront’s /products/<handle>. It is the one destination this screen could not otherwise reach: everything else here is our record of the listing, and the question a reader asks after reading it is what does the customer actually see.
It is off — dashed and unclickable — rather than absent when there is nowhere to go, and the tooltip says which nowhere. Built but never listed, draft product, not on the storefront and active and syncing but not published are three different situations and only the last one is deliberate. An icon that simply vanished would make them all read as we failed to build a link. Four of the 33 rows are off, and each is a fixture that exists to say something: L‑202 and L‑203 were built by B‑0007 and never pushed, Sea Moss is a draft, and Avinol PM is the row that proves active ≠ published.
The drawer used to answer identity, lineage and bookkeeping. Those are the questions you ask about a listing; they are not the listing. The artefact a channel listing IS — the title, the description as it published, the item specifics, the assembled images, the price and the fulfilment terms — had nowhere to be read once the job that made it was closed. So the drawer now carries the whole of listing-builder.html #stage=review: the masthead, the four section cards with their status pills, the specifics table with its provisional and blank markers, and the image strip.
The shape is ported, not re-drawn. frmast, frsec, frimgs2, aslot, spectbl and claim are the builder’s CSS, copied verbatim. A reviewer who learned that shape at the end of a job should not have to learn a second one to read the same facts a week later.
The content is derived from the row, never fixtured a second time. The title, the composition, the pack and the price are the grid’s; the description, the nine item specifics and the image slots come out of the same templates the job used, run over this row. Twenty-nine hand-written copies is precisely how a drawer comes to disagree with the row that opened it — and the disagreement would be invisible, because nothing puts the two side by side.
The blanks keep their reasons. A combo shows Type, Formulation, Main purpose, Department, Country and Active ingredients blank, each with the sentence that says why — members differ, no tag every member carries. Two of nine filled is the honest number, and it is the number the builder sends. The three provisional values are values with no field behind them yet, not values we are unsure of.
A draft does not claim to have published. The section pill reads drafted — never published, and the caption over the description says as it would publish. The same body of content, three different tenses, because L‑202 has never been on eBay and L‑198 has been and stopped.
Seven named slots, in the practiced order, each with the bar that says whose it is: black is custom — generated for this listing and nothing else; grey is shared — the catalog product’s, reused by every listing built from it. That distinction is the one that costs money: a shared slot is free to reuse and a custom hero is a generation.
Under the strip is the table a thumbnail cannot replace — filename, source and version, pixel size, origin (generated · gpt-image against image library · CP-0102), who approved it and when, and the cost. A reviewer asking why did this listing cost $0.09 or is this hero the one we approved is asking about that table, not about the picture.
The pictures are stand-ins on every product but Memopryl, and they say so. mockups/sample-images/ holds photography for CP‑0102 only. A Beet Root slot drawing a visibly Memopryl bottle with no marker would read as a data error rather than as a missing file, so each stand-in wears the label. The filenames, sizes and provenance beside them are the row’s own.
Same modal as the builder’s Preview listing, and it opens from the drawer header rather than the footer — the question what does this look like is asked while reading the fields, not after. The image strip is also the way in: clicking any slot opens the preview on that image.
The two channels get two previews, because they are two different objects. eBay’s is one listing: condition, quantity, shipping, returns, item specifics. Shopify’s is a product page with a chip per variant — and pressing a chip moves the price, the compare-at strike, the SKU, and the count, because a 6-pack under a panel that says 60 capsules (1 bottle) is a lie the reader would not catch.
The storefront preview is where the silent failure becomes visible. MEMP-6-OB-VD renders a normal, complete, buyable-looking product page and the In the cart row reads no — no CheckoutChamp metafield, and the storefront does not say so. That is the whole defect drawn once: the customer’s view has no symptom.
Marketplace chrome is deliberately omitted — similar items, sponsored carousels, payment widgets, seller cards. eBay and Shopify render those and we do not control them; drawing them would be a fiction a reviewer might trust.
An angle is who a listing is pitched at — Over 50, Gamers — or the moment it is sold into. Until 2026-09-14 the eBay brief drew angles out of the Benefits list, so Joints (what a product does) and Over 50 (who buys it) sat in one row of pills. Paul split them: Benefits stays on the product, and angles become their own list (RI-744).
The list belongs to listing jobs in general, not to one builder, and there is no screen for listing jobs in general. This screen is the nearest thing: it is already the one place that holds every channel’s listings as tabs. So the list is a tab here, set apart from the channel tabs because it is not a channel, and Settings › Marketing angles in the sidebar lands on it, the way Image types & tags lands on the image library’s admin tab (RI-745). That is the house pattern: a vocabulary is administered on the list screen of the thing that uses it.
It is the same editor and the same array as Benefits and Tags on the catalog list: _vocab-admin.js over MRTagField.vocab('angles'), so the list this tab edits is the list every creative brief searches. Used counts listing jobs, not products. The list is open — a brief may add an angle, and the add says it was global (RI-746); renaming and removing stay here.
Considered and not drawn: a separate Settings screen (a page with one list on it and nothing it is used by in sight), and a tab inside each builder (two builders, two lists — the drift _tagfield.js exists to stop).
Open — needs Paul
1 · There is no margin column, and there probably should be. Gross margin is COGS-only (MR-084) and every row here clears 80%, so a red-under-floor cell computed from it would never fire — a number that looks like the floor check and is not. Net margin is the real check and needs price − cost − postage − fee − ad, and postage comes from the package resolver, which this grid does not call. Per unit ships instead. The question is whether a read-time grid should be running the resolver at all.
2 · Memopryl on Shopify is 1 / 3 / 6, and RI-256 measured Vital Depot’s shape as 1 / 3 / 5. The SKU Map fixture predates the store measurement and existing SKUs are never renamed (O21). Left standing rather than quietly corrected. Is the 6-pack a legacy shape to retire, or is 1/3/5 not the whole rule?
3 · The accounts list’s Listings column counts purchasable units, not records. An eBay listing is one; a Shopify variant is one. That is what makes CA-01’s 12 agree with four products here. It is defensible and it is not obvious from the column’s label — if it stays, the label should probably say units.
4 · The list sheet has diverged from the two screens it was ported from. Per-column wrapping, the 96px floor and the locked headers live only here; source-formulas-list.html and sales-channel-accounts-list.html still carry the 64px floor, no wrap flag and a header that scrolls away. Their lists are shorter, so nothing looks broken — which is exactly how three copies of one component come to behave differently. Port it back, or make the sheet a shared asset the way _screens-nav.js and _image-module.js are.
5 · The drawer’s content is derived, and a real listing’s is stored. Here the description and the nine specifics are re-run from the template over the row, which is right for a mockup and wrong for the build: what published is a fact, and re-deriving it would quietly show today’s template over yesterday’s listing. The build must read the stored draft the job wrote. Named here because the screen cannot show the difference and the requirement has to.
6 · Nothing re-checks the claim gate after publication. The Content card says claim gate — clear, and that was true at build time. A claim library that tightens afterwards leaves live listings carrying claims that would now fail, and nothing on this screen would know. Either the gate re-runs against live listings on a schedule, or the pill should say clear at build time and stop implying more.
8 · Should an angle have a note for the prompt? Today an angle is a name and a stored value, and the prompt receives the name. Over 50 says enough on its own; Back to school may need a sentence of what it means for copy. Not drawn until an angle is shown to need one.
7 · Nothing here pages. 41 rows is the whole fixture and the filters run over an array. eBay accounts reach thousands, and at that size the column picker and the sort are not enough — saved views and server-side paging both become real. Neither is drawn.