{% extends "base.html" %} {% block title %}Inventory · foilstack{% endblock %} {% block status %}{{ totals.count }} in stock · {{ totals.market|money }} at market{% if totals.sold_rows %} · {{ totals.sold_rows }} sold, {{ totals.realised|money }} realised{% endif %}{% endblock %} {% block screen %} {# Nowrap for the reason the listing run is: at 1000px, the width the README animation is captured at, anything else on this bar pushed the primary button onto a row of its own and the filter row below it then read as three rows of chrome above the table. The give is the search box, which both takes the leftover width and is the only thing here still usable narrower — which is why the status chips went down to the filter row and not beside it. #}

Inventory

{# The widest thing on the bar, and the only one that grows: it is how a seller reaches a card in an inventory too large to browse to, and every other control here is either a fixed count or a button. The placeholder names what the search actually reads. It used to say "sku" while nothing in `narrow` looked at one, which is the kind of promise a seller only finds out about by pasting a SKU from a sold notification and being told they own no such card. #} {# Three numbers answering three questions: where this page sits, how many lines the filters matched, and how many there are in all. Collapsed to one they contradict each other — "100 lines" above a filter chip reading 4,231 reads as the filter having lost most of the inventory. The window is a separate clause rather than another "of". Written "1–100 of 138 of 152 lines" the sentence has two of them, and the middle number stops being readable as either bound. #} {# `data-plain` is the same label without the window, for when a selection is active. The bar is `bar-nowrap` and its only give is the search box, which by this point has already shrunk to its minimum — so all three clauses at once truncate to "740 cards …" and the number the seller most needs is the one that gets cut. The window is on the pager and in the selection bar by then, and the selection is not anywhere else. #} {% if last_page > 1 %}showing {{ row_from }}–{{ row_to }} · {% endif %}{{ matched_lines }}{% if narrowed %} of {{ total_lines }}{% endif %} lines {# Disabled rather than hidden, unlike the Delete button beside it. That one is absent at rest because it is destructive and there is nothing to mis-click; this one is the only thing on the screen that says selecting rows leads anywhere, so it has to stay visible to be the instruction. It starts disabled in the markup rather than waiting for `sync()`, because a live primary button for one parse is one a fast hand can press — and until this it submitted no ids at all, which `/listings` reads as the whole inventory and prices a run over everything the account owns. The filter the seller was looking at is not on that form, so the screen they left said 75 unlisted and the one they arrived at answered for all of it. #}
{# Every way of narrowing the screen, on one row: which rows are in play, then the facets. The status chips moved down out of the bar when the search box grew to fill it — they are a filter, they belong beside the other filters, and the alternative was a bar that wrapped at the width the README animation is captured at. The facets are menus rather than rows of chips because a row painted every value it could fit and then admitted to hiding the rest: at ten sets that is four lines of chrome above the table, and at a hundred it is a "+90" the seller cannot click. A menu shows one value closed and all of them open. The cost is that a closed control has to say what it is doing, which is what `any_label` and the picked-value summary are for — a filter you have to open a menu to discover is the same fault the old row avoided by shouting. #}
{% for c in status_chips %} {{ c.label }} {{ c.count }} {% endfor %}
{% for f in facets %} {# Picked values, for the summary. `options` always carries them, painted at zero if that is their count, so this cannot miss a filter in force. #} {% set picked = f.options|selectattr('on')|list %} {% endfor %} {% if facets|selectattr('more')|list %} {# Only when it has something behind it. A disclosure that discloses nothing is furniture, and this row has enough of that risk already. A link, because that is what every other control on this row is and it is what makes the two folded filters reachable with no JavaScript at all. The script below intercepts it to save the round trip; without one it is an ordinary page load that renders the row unfolded. #} More filters → {% endif %} {% if filtered %} clear {{ filtered }} filter{{ '' if filtered == 1 else 's' }} {% endif %}
{# The filter this screen is showing, carried onto the run. A selection is not always a list of ids any more. One page of a hundred lines is around 740 copies, which is a 6.4 KB querystring — already inside what a proxy will commonly accept, before a shop with twenty copies of a staple is considered — and "everything matching" would be a quarter of a megabyte. So the filter travels instead and `/listings` resolves it through the same `inventory.narrow` this screen used. `sel` is what says which reading applies, and it is empty at rest. An absent mode selects the ids and nothing else: `/listings` once read "no ids" as "everything the account owns", with the filter nowhere on the form, so a screen reading `Not listed 75` handed off a run priced over the whole inventory. Breadth has to be something asked for. #} {% for name, value in sel_fields %}{% endfor %} {% if last_page > 1 %} {# Only where there is more to select than is on screen. On a single page "select all matching" and the header checkbox are the same act, and offering both invites the seller to look for a difference. #} {% endif %} {# Says which rows it means, because the table is a page of a larger result and the box cannot reach past it. A select-all that silently covers a hundred of four thousand lines is the whole hazard of paging a screen that feeds a listing run. #} {# A sortable heading is a link, not a script: the order is in the URL, so a sorted view is one a seller can bookmark and send. #} {% for r in rows %} {% endfor %}
{{ sorts.name|sortlink('CARD') }} {{ sorts.set|sortlink('GAME / SET') }} {{ sorts.quantity|sortlink('QTY') }} CONDITION PRINTING {{ sorts.cost|sortlink('COST') }} {{ sorts.market|sortlink('MARKET') }} {{ sorts.margin|sortlink('MARGIN') }} {{ sorts.listed|sortlink('LISTED') }}
{# One hidden input per copy, so the server contract stays "a list of inventory ids" no matter how the UI groups them. #} {% for i in r.ids %}{% endfor %} {# The catalogue image, not a scan: with several copies behind this line, picking one of their scans to represent it is arbitrary. #} {{ r.name }} {% if r.sold %}sold {% elif r.sold_quantity %}{{ r.sold_quantity }} sold{% endif %} {{ r.game }}{% if r.set_name %} / {{ r.set_name }}{% endif %} {{ r.quantity }} {{ r.conditions }} {{ r.printing_label }} {# Ahead of the "guessed" mark: a finish with no price behind it is the worse of the two and stays quiet on a single-printing card, which is most of the cards it happens to. "No price", not "no printing". The catalogue names printings it has no market figure for, and a foil printing nobody will pay for leaves this row priced off the non-foil just as surely as no foil printing at all would. Reading those two as the same fact is what this pill was doing, and it went quiet on the cards it was written for. #} {% if r.finish_unpriced %}! {% elif r.guessed %}?{% endif %} {{ r.cost|money if r.cost is not none else '—' }} {{ r.market|money if r.market is not none else '—' }} {{ r.margin_pct ~ '%' if r.margin_pct is not none else '—' }} {{ r.listed_label }}
{% if not rows %}
{% if show == 'printing' %}every card is priced on a printing you chose {% elif filtered and total_lines %}no card matches every filter · clear them {% elif q %}nothing matches “{{ q }}” {% else %}nothing confirmed yet · import some scans to get started{% endif %}
{% endif %}
{# Inside the scroller and after the table, so it sits at the end of the rows rather than floating in the chrome. Absent entirely on a single page: a paging control that cannot page is furniture. #} {% if last_page > 1 %} {% endif %}
{% endblock %} {% block scripts %} {% endblock %}