site: editorial tooling, grid masonry fix, new reviews + finds

UI / dev tooling
- /find/ index: sort by file birthtime desc (sub-day chronology even when
  many finds share the same frontmatter date); render the captured-at on
  its own line under the host (~0.65rem mono muted)
- New dev-only filter at the top of /find/: All / Not in lists, persisted
  in localStorage; "not in lists" hides finds referenced by any superpost
- New editorial-tags chip strip on each find card (dev-only): selected
  tags visible by default, full picker on hover/focus-within with a "…"
  button for arbitrary tag entry; clicks copy /edit-find-tag <slug> <tag>
  to the clipboard via the existing EditConfirm singleton
- New optional editorialTags: string[] field on the find collection
- New src/data/editorial-tags.json — master list of available tags (initial
  set: ["delete"]); display order follows file order

/grid/ now uses the JS Masonry component instead of CSS column-count, so
the middle column no longer "dips"; tile hover updated to match the home
page's accent-glow / no-translate style

Content
- New review: apex-markdown-processor (graduated from find of same slug;
  hero reused from the find folder)
- New review: sindre-sorhus-older-mac-apps (graduated; intentionally
  hero-less per the source page's text-only design)
- New review: superkey (added by user; hero wired in src/lib/hero.ts)
- 24 new finds captured by /daily-finds across travel, jokes, quotes,
  product picks, and indie tools
- 2 new finds superposts: 2026-05-04-a-small-atlas, 2026-05-04-off-until-needed

Misc
- daily-finds.log.md and candidate-sources.md updated with the day's runs
- build-finds skill prompt iterated by user
This commit is contained in:
2026-05-04 18:57:04 -04:00
parent 1ecdc10443
commit 7e1b200bea
59 changed files with 927 additions and 75 deletions
+117 -4
View File
@@ -1,15 +1,45 @@
---
import fs from 'node:fs';
import path from 'node:path';
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import Masonry from '../../components/Masonry.astro';
import FindCard from '../../components/FindCard.astro';
import EditAction from '../../components/EditAction.astro';
import { getAllFinds } from '../../lib/find-items';
import editorialTags from '../../data/editorial-tags.json';
const INITIAL_GRID = 18;
const BATCH_SIZE = 18;
const finds = await getAllFinds();
const findRoot = path.join(process.cwd(), 'src/content/find');
function capturedAtFor(slug: string): Date {
try {
const stat = fs.statSync(path.join(findRoot, slug, 'index.mdx'));
// birthtime is creation time (true on macOS/Windows). Falls back to mtime
// on filesystems without birthtime support.
return new Date(stat.birthtimeMs || stat.mtimeMs);
} catch {
return new Date(0);
}
}
// Decorate each find with its file-creation timestamp, then sort by that
// (newest first) — gives true chronological order even when many finds
// share the same day in their frontmatter `date`.
const decorated = (await getAllFinds())
.map((item) => ({ item, capturedAt: capturedAtFor(item.id) }))
.sort((a, b) => b.capturedAt.valueOf() - a.capturedAt.valueOf());
const finds = decorated.map((d) => d.item);
const lastUpdated = finds[0]?.data.date;
// Slugs referenced by any superpost in the `finds/` collection — dev-only filter.
const inListSlugs = new Set(
(await getCollection('finds')).flatMap((l) => l.data.items)
);
const notInListCount = finds.filter((f) => !inListSlugs.has(f.id)).length;
---
<BaseLayout
@@ -19,9 +49,29 @@ const lastUpdated = finds[0]?.data.date;
lastUpdated={lastUpdated}
noindex
>
<Masonry initial={INITIAL_GRID} batch={BATCH_SIZE}>
{finds.map((f) => <FindCard item={f} />)}
</Masonry>
{import.meta.env.DEV && (
<div class="find-filter" role="group" aria-label="Filter">
<button type="button" data-find-filter-set="all" aria-current="true">
All ({finds.length})
</button>
<button type="button" data-find-filter-set="not-in-lists">
Not in lists ({notInListCount})
</button>
</div>
)}
<div data-find-filter="all">
<Masonry initial={INITIAL_GRID} batch={BATCH_SIZE}>
{decorated.map(({ item, capturedAt }) => (
<FindCard
item={item}
inList={inListSlugs.has(item.id)}
capturedAt={capturedAt}
availableTags={editorialTags}
/>
))}
</Masonry>
</div>
{import.meta.env.DEV && (
<div class="edit-floating-toolbar" data-find-toolbar>
@@ -50,6 +100,34 @@ const lastUpdated = finds[0]?.data.date;
});
refresh();
}
// Dev-only filter: toggle "all" vs "not-in-lists" on the masonry wrapper.
const wrapper = document.querySelector<HTMLDivElement>('[data-find-filter]');
const filterButtons = document.querySelectorAll<HTMLButtonElement>('[data-find-filter-set]');
const FILTER_KEY = 'find-filter';
if (wrapper && filterButtons.length) {
type FilterValue = 'all' | 'not-in-lists';
const apply = (value: FilterValue) => {
wrapper.setAttribute('data-find-filter', value);
for (const b of filterButtons) {
if (b.dataset.findFilterSet === value) b.setAttribute('aria-current', 'true');
else b.removeAttribute('aria-current');
}
try { localStorage.setItem(FILTER_KEY, value); } catch (e) {}
};
let saved: string | null = null;
try { saved = localStorage.getItem(FILTER_KEY); } catch (e) {}
if (saved === 'all' || saved === 'not-in-lists') apply(saved);
for (const b of filterButtons) {
b.addEventListener('click', () => {
const v = b.dataset.findFilterSet;
if (v === 'all' || v === 'not-in-lists') apply(v);
});
}
}
</script>
<style>
@@ -78,5 +156,40 @@ const lastUpdated = finds[0]?.data.date;
color: var(--fg);
font-weight: 700;
}
.find-filter {
display: inline-flex;
gap: 0;
margin-bottom: 1.25rem;
border: 1px solid var(--rule);
border-radius: 999px;
overflow: hidden;
}
.find-filter button {
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.04em;
background: none;
border: 0;
padding: 0.4rem 0.95rem;
margin: 0;
color: var(--muted);
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.find-filter button + button {
border-left: 1px solid var(--rule);
}
.find-filter button:hover { color: var(--accent); }
.find-filter button[aria-current="true"] {
background: var(--fg);
color: var(--bg);
}
/* Hide in-list cards when the filter is set to not-in-lists. */
[data-find-filter="not-in-lists"] [data-in-list="true"] {
display: none;
}
</style>
</BaseLayout>
+41 -62
View File
@@ -1,6 +1,7 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import Masonry from '../components/Masonry.astro';
import { heroes } from '../lib/hero';
const reviews = (await getCollection('reviews')).sort(
@@ -24,79 +25,56 @@ const lastUpdated = reviews[0]?.data.date;
lastUpdated={lastUpdated}
noindex
>
<ul class="grid">
{
tiles.map((tile) => (
<li>
<a href={tile.href} class:list={['tile', { 'text-only': !tile.hero }]}>
{tile.hero?.kind === 'image' && (
<img
class="media"
src={tile.hero.src.src}
width={tile.hero.src.width}
height={tile.hero.src.height}
loading="lazy"
decoding="async"
alt={tile.name}
/>
)}
{tile.hero?.kind === 'video' && (
<video
class="media"
src={tile.hero.src}
autoplay
loop
muted
playsinline
preload="none"
aria-label={tile.name}
/>
)}
<div class="text">
<span class="name">{tile.name}</span>
<span class="subtitle">{tile.subtitle}</span>
</div>
</a>
</li>
))
}
</ul>
<Masonry>
{tiles.map((tile) => (
<article>
<a href={tile.href} class:list={['tile', { 'text-only': !tile.hero }]}>
{tile.hero?.kind === 'image' && (
<img
class="media"
src={tile.hero.src.src}
width={tile.hero.src.width}
height={tile.hero.src.height}
loading="lazy"
decoding="async"
alt={tile.name}
/>
)}
{tile.hero?.kind === 'video' && (
<video
class="media"
src={tile.hero.src}
autoplay
loop
muted
playsinline
preload="none"
aria-label={tile.name}
/>
)}
<div class="text">
<span class="name">{tile.name}</span>
<span class="subtitle">{tile.subtitle}</span>
</div>
</a>
</article>
))}
</Masonry>
<style>
.grid {
column-count: 3;
column-gap: 1rem;
list-style: none;
margin: 0;
padding: 0;
}
@media (max-width: 880px) {
.grid { column-count: 2; }
}
@media (max-width: 520px) {
.grid { column-count: 1; }
}
.grid > li {
break-inside: avoid;
display: block;
margin: 0 0 1rem;
}
.tile {
display: block;
border-radius: 8px;
overflow: hidden;
border: 1px solid var(--rule);
border: 2px solid var(--rule);
background: var(--bg);
color: inherit;
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
transition: box-shadow 0.18s ease, border-color 0.18s ease;
}
.tile:hover {
text-decoration: none;
transform: translateY(-2px);
border-color: color-mix(in srgb, var(--accent) 40%, var(--rule));
box-shadow: 0 8px 24px -10px rgba(0, 0, 0, 0.18);
border-color: color-mix(in srgb, var(--accent) 50%, var(--rule));
box-shadow: 0 0 20px -4px color-mix(in srgb, var(--accent) 30%, transparent);
}
.tile:hover .name { color: var(--accent); }
@@ -112,6 +90,7 @@ const lastUpdated = reviews[0]?.data.date;
display: flex;
flex-direction: column;
gap: 0.2rem;
background: var(--card-fill);
}
.name {
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
+2 -2
View File
@@ -5,8 +5,8 @@ import Masonry from '../components/Masonry.astro';
import { heroes } from '../lib/hero';
import { buildOgImage, buildItemList, WEBSITE, ORGANIZATION } from '../lib/seo';
const HERO_SLUG = 'alt-tab';
const INITIAL_GRID = 7;
const HERO_SLUG = 'superkey';
const INITIAL_GRID = 12;
const BATCH_SIZE = 7;
const reviews = (await getCollection('reviews')).sort(