Tables
Columns
One column per attribute, less four kinds a table leaves off: encrypted columns, the primary key, the two
timestamps and JSON. The order is banded — an enum first, then foreign keys, scalars, booleans, long values,
dates, and counters last — with the schema’s order inside each band. Headings come from
human_attribute_name, a date or a time headed without its at.
A table opens with one narrow column per action a row has, an eye and a pencil, each drawn only where its
action is routed. A resource routed destroy with no edit draws a third, the trash icon, since no
page would carry the button.
A cell reads as what its column is of: a key as the label it points at, linked; a boolean as
Yes or No; a time in the reader’s zone with how long ago in a tooltip; a web address as its host;
a counter as the figure, or 1,204 comments where the table is wide.
Sorting
A heading is a link where an index covers the column, and on every counter and timestamp — never on a foreign key, whose cell shows a label from another table. The first click sorts downward, the second up, and rows with nothing in the column come last either way.
Searching
The box looks through every indexed string column the table shows, plus the label behind a key whose table
is too long to list, and says so in its placeholder: Filter by title or slug. Typing submits after a
moment of quiet and replaces only the table. What matched is marked in the cell that matched it. A model with
only encrypted columns to search gets a box asking for an exact value.
Filters
One menu per enum, per boolean and per foreign key whose table has 200 rows or fewer, each ticking more
than one value and opening with All … as the way back. Options show how many rows they hold and are
ordered by it, except under a record, where the number would be of a table the page is not showing. A model
adds a menu of its own in filter_fields.
Pages
A table opens at 15 rows, and the footer offers 100 per page. The choice is a cookie, checked against
those two sizes, so no address carries a page size and nothing is written to the host.
Bookmarks
Recourse.bookmarks = -> { Bookmark.where user: Current.user }
A Proc, because at boot the viewer is nobody. Every model with a has_many at that class opens its table
with a square to keep a row by, kept rows first and tinted. A click posts in the background and asks nothing,
since it is undone by clicking again.
Live refreshes
With turbo-rails, an open index redraws when a row changes, morphed in place with the scroll, the
search box and any open menu kept. A model opts out with recourse_broadcasts?.
The cache
The table is a fragment keyed on the relation, its newest updated_at, the counts, the viewer’s bookmarks
and their time zone, so nothing has to expire it. A sorted, searched or filtered table is drawn live. A
touch: true declared only to expire a table can go.
On a phone
The sidebar is a row of icons across the top and every row of the table a stacked block. A pair of arrows at the foot of the sidebar puts the words back beside the icons.