Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 46 additions & 41 deletions src/features/guides/routes/GuidesIndexRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,102 +28,107 @@ export default function GuidesIndexRoute(): ReactElement {
</p>
</header>
<div className={styles.guideList}>
{guides.map((guide) => (
<GuideCard key={guide.slug} guide={guide} />
))}
{/* Interactive page, not a markdown guide, so it is listed here
explicitly instead of coming from getAllGuides(). */}
{/* Deliberately rendered FIRST, above the date-sorted markdown guides
from getAllGuides() — this is not a sorting bug, so please do not
"fix" it by moving the card back down. The catalogue is the newest
work and gets the most prominent slot on the page (issue #255,
overriding the bottom placement chosen in #241).

Hosted in its own repository and published to
leoncheng.dev/agent-skills, so this card links out of the SPA with
a plain anchor rather than a router Link. */}
<article className={styles.guideCard}>
<div className={styles.cardChrome}>
<span className={styles.dots} aria-hidden="true">
<span />
<span />
<span />
</span>
<p className={styles.cardPath}>~/guides/opencode-remote-control</p>
<p className={styles.cardPath}>~/guides/agent-skills</p>
</div>
<div className={styles.cardBody}>
<h2 className={styles.cardTitle}>
<span className={styles.prompt} aria-hidden="true">
$
</span>
<Link to="/guides/opencode-remote-control">
OpenCode Remote Control
</Link>
<span className={styles.betaBadge}>BETA</span>
<a href="https://leoncheng.dev/agent-skills/">Agent Skills</a>
</h2>
<p className={styles.description}>
Control local OpenCode sessions from your phone over a private
tailnet, with ntfy pushes that deep-link into the exact session.
Interactive setup builder, daily commands, notification
customization, and troubleshooting on one page.
A catalogue of portable agent skills: reusable, on-demand
workflow instructions a coding agent loads only when a task
calls for them. Written for OpenCode and compatible with any
agent that reads a SKILL.md file, so the same skill works in
Claude Code or Cursor. Every skill page carries copy-paste
install commands.
</p>
<p className={styles.cardMeta}>
<span>updated 2026-08-13</span>
<span className={styles.separator} aria-hidden="true">
·
</span>
<span>interactive guide</span>
<span>external site</span>
<span className={styles.separator} aria-hidden="true">
·
</span>
<span>
<a href="https://github.com/leoncheng57/opencode-remote-control-and-notifications">
<a href="https://github.com/leoncheng57/agent-skills">
GitHub ↗
</a>
</span>
</p>
<ul className={styles.tagRow} aria-label="Guide tags">
<li className={styles.tag}>#agents</li>
<li className={styles.tag}>#opencode</li>
<li className={styles.tag}>#workflow</li>
</ul>
<p className={styles.cardCta}>
<Link to="/guides/opencode-remote-control">read guide &rarr;</Link>
<a href="https://leoncheng.dev/agent-skills/">browse skills ↗</a>
</p>
</div>
</article>
{/* Hosted in its own repository and published to
leoncheng.dev/agent-skills, so this card links out of the SPA
with a plain anchor rather than a router Link. It is listed last
because everything above it is a first-party page on this site. */}
{guides.map((guide) => (
<GuideCard key={guide.slug} guide={guide} />
))}
{/* Interactive page, not a markdown guide, so it is listed here
explicitly instead of coming from getAllGuides(). */}
<article className={styles.guideCard}>
<div className={styles.cardChrome}>
<span className={styles.dots} aria-hidden="true">
<span />
<span />
<span />
</span>
<p className={styles.cardPath}>~/guides/agent-skills</p>
<p className={styles.cardPath}>~/guides/opencode-remote-control</p>
</div>
<div className={styles.cardBody}>
<h2 className={styles.cardTitle}>
<span className={styles.prompt} aria-hidden="true">
$
</span>
<a href="https://leoncheng.dev/agent-skills/">Agent Skills</a>
<Link to="/guides/opencode-remote-control">
OpenCode Remote Control
</Link>
<span className={styles.betaBadge}>BETA</span>
</h2>
<p className={styles.description}>
A catalogue of portable agent skills: reusable, on-demand
workflow instructions a coding agent loads only when a task
calls for them. Written for OpenCode and compatible with any
agent that reads a SKILL.md file, so the same skill works in
Claude Code or Cursor. Every skill page carries copy-paste
install commands.
Control local OpenCode sessions from your phone over a private
tailnet, with ntfy pushes that deep-link into the exact session.
Interactive setup builder, daily commands, notification
customization, and troubleshooting on one page.
</p>
<p className={styles.cardMeta}>
<span>external site</span>
<span>updated 2026-08-13</span>
<span className={styles.separator} aria-hidden="true">
·
</span>
<span>interactive guide</span>
<span className={styles.separator} aria-hidden="true">
·
</span>
<span>
<a href="https://github.com/leoncheng57/agent-skills">
<a href="https://github.com/leoncheng57/opencode-remote-control-and-notifications">
GitHub ↗
</a>
</span>
</p>
<ul className={styles.tagRow} aria-label="Guide tags">
<li className={styles.tag}>#agents</li>
<li className={styles.tag}>#opencode</li>
<li className={styles.tag}>#workflow</li>
</ul>
<p className={styles.cardCta}>
<a href="https://leoncheng.dev/agent-skills/">browse skills ↗</a>
<Link to="/guides/opencode-remote-control">read guide &rarr;</Link>
</p>
</div>
</article>
Expand Down
18 changes: 18 additions & 0 deletions src/features/guides/routes/guides-route.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,24 @@ describe('guides index route', () => {
expect(card.queryByText(/updated 20/)).not.toBeInTheDocument()
expect(card.queryByText(/min read/)).not.toBeInTheDocument()
})

it('leads the index with the agent skills card, ahead of the date-sorted guides', () => {
render(
<MemoryRouter initialEntries={['/guides']}>
<App />
</MemoryRouter>
)

const skillsCard = screen.getByRole('link', { name: 'Agent Skills' }).closest('article')!
const cards = Array.from(skillsCard.parentElement!.querySelectorAll(':scope > article'))

// Deliberate placement, not a sorting bug: the catalogue outranks the
// markdown guides even though those are sorted by updatedAt descending.
// Pinned here so a future refactor cannot quietly re-sort it back down.
expect(cards.length).toBeGreaterThan(1)
expect(cards[0]).toBe(skillsCard)
expect(cards.indexOf(screen.getByRole('link', { name: GUIDE_TITLE }).closest('article')!)).toBeGreaterThan(0)
})
})

describe('guide site chrome', () => {
Expand Down
Loading