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
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ dist/
# dependencies
node_modules/

# logs
npm-debug.log*

# macOS-specific files
.DS_Store

# AI tools
/.crush/
/.claude/
1 change: 0 additions & 1 deletion .npmrc

This file was deleted.

62 changes: 62 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# AGENTS.md

This file provides guidance to AI coding agents when working with code in this repository.
Comment on lines +1 to +3

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed this project doesn't have AGENTS.md yet, so created one using /init command in Claude Code. But please feel free to rewrite/amend this in the future if needed.


## Project

Static site for the okTurtles Podcast (https://podcast.okturtles.org/), built with Astro 7, Vue 3 islands and SCSS. The site's main outputs are the episode pages and the podcast RSS feed at `/rss.xml`.

## Commands

```sh
deno install # install dependencies into node_modules/ (after cloning or changing deno.json)
deno task dev # dev server
deno task build # static build into dist/
deno task preview # serve the built dist/```
```

There is no lint, type-check or test script. `deno task build` is the only automated check; after touching the feed, inspect `dist/rss.xml` (the README lists external feed validators).

- The project runs on Deno 2.7.0 or newer, with no `package.json`. Dependencies are npm packages pinned to exact versions in the `imports` map of `deno.json`.
- Tasks run Astro with the `--allow-*` flags in `deno.json` rather than `--allow-all`. Writes are limited to the project folder and network access to localhost. `--allow-run` and `--allow-ffi` are required by the build tools and are not restricted.
- The `ASTRO_TELEMETRY_DISABLED` variable (all tasks) and the `ASTRO_DISABLE_UPDATE_CHECK` variable (`dev` only) are required: without them Astro attempts things the permissions do not allow.
- The `noExternal` and `alias` settings under `vite.resolve` in `astro.config.mjs` are required for the build.
- Episode MP3s are stored in Git LFS.

## Architecture

### Episodes are Markdown pages

Each episode is `src/pages/episodes/<n>.md`, routed by Astro's file-based routing and rendered through `src/layouts/EpisodePageLayout.astro` (set via the `layout` frontmatter key). There is no content collection; the frontmatter is the single source of truth for the page, the episode lists, the tag pages and the RSS feed. Its shape is the `Episode` interface in `src/types.ts`.

Adding an episode means adding:

1. `src/pages/episodes/<n>.md` with the full frontmatter (copy the latest episode)
2. `public/media/audios/episodes/<n>.mp3` (LFS)
3. `public/images/episode-covers/ep<n>.jpg` (`coverImage` is the bare filename; the directory is prepended in code)

Frontmatter fields that feed the RSS `<enclosure>` and `<guid>` must be accurate: `filesize` is the MP3 size in bytes, `filetype` its MIME type, `duration` is `HH:MM:SS` (or `MM:SS`), and `guid` is a unique UUID that must not change once published. `episode` (number) is the sort key everywhere.

### Episode data access

`getAllEpisodes()` in `src/helpers.ts` loads all episodes with `import.meta.glob('./pages/episodes/*.{md,mdx}', { eager: true })` and attaches `details` (frontmatter + compiled HTML as `epContent`). Tag helpers there derive everything from episode `tags`; there is no separate tag registry. Tag URLs replace whitespace with underscores (`getTagLink` / `whiteSpaceToUnderscore`), and `src/pages/tag/[tag].astro` must use the same transform in `getStaticPaths`.

`EpisodeList.vue` is hydrated with `client:load` and calls `getAllEpisodes()` itself when no `episodeList` prop is passed, so that helper also runs in the client bundle.

### RSS feed

`src/pages/rss.xml.js` builds the feed XML by hand using the string helpers in `src/utils/rss-generation.ts` (not `@astrojs/rss`), following Apple Podcasts requirements and the PSP-1 spec linked in the source comments. Channel-level metadata comes from `src/constants.ts`; `SITE_URL` there is also used as Astro's `site` in `astro.config.mjs`. `generateXMLTag` escapes content unless it contains a CDATA block or `noEscape` is passed. Each item carries the post as sanitized HTML (`content:encoded`, `itunes:summary`) and as plain text (`description`).

### Layout and client-side pieces

- `PageLayout.astro` wraps every page (`BaseHead`, `Header`, `Footer`). It takes `pageType` (`'default' | 'episode'`), which sets the `is-<pageType>` body class and the header variant, and puts the episode number on `<body data-episode>`, which `HeaderButtons.vue` reads on the client.
- Plyr is not an npm dependency. `EpisodePageLayout.astro` injects `public/third-party/plyr.3.7.8.min.js` as an inline script through the `head-append` slot, and `AudioPlayer.vue` uses the global `Plyr`. A `?play` query parameter on an episode URL triggers autoplay.
- Cross-island state uses nanostores (`src/store/toast.ts`, consumed via `@nanostores/vue`).

### Styles

Global styles are loaded once via `src/styles/main.scss` (imported in `BaseHead.astro`). Component `<style lang="scss">` blocks pull in variables, colors and mixins with `@use '@/styles/variables' as *;` (e.g. the `from($tablet)` breakpoint mixin). Class naming: `c-` for component-scoped classes, `l-` for layout, `is-` for state modifiers. `@/` aliases `src/` (`tsconfig.json`, plus the Vite alias in `astro.config.mjs`).

## CI

The only workflows (`.github/workflows/pull-review*.yml`) run an AI PR review when a member, owner or collaborator comments `/review` on a pull request. Nothing builds or deploys from CI.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,36 @@

okTurtles Podcast.

## Development

This project is built with [Astro](https://astro.build/) and runs on [Deno](https://deno.com/) instead of Node.js and npm.

### Requirements

- [Deno](https://docs.deno.com/runtime/getting_started/installation/) 2.7.0 or higher. Check your version with `deno --version` and update it with `deno upgrade`.
- [Git LFS](https://git-lfs.com/), only if you need the episode audio files (see below).

### Setup

```sh
deno install # install the dependencies
```

The episode audio files are stored in Git LFS. Without it the site still runs and builds, but the audio does not play. You need it to add an episode or to build the site for deployment:

```sh
git lfs install # once per machine
git lfs pull # download the episode audio files
```

### Commands

| Command | What it does |
|---|---|
| `deno task dev` | Starts the development server at http://localhost:4321 |
| `deno task build` | Builds the site into the `dist/` folder |
| `deno task preview` | Serves the built `dist/` folder at http://localhost:4321 |

## Feed validation services

- https://www.castfeedvalidator.com/
Expand Down
10 changes: 10 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
import { SITE_URL } from './src/constants';
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import vue from '@astrojs/vue';
Expand All @@ -11,6 +12,15 @@ export default defineConfig({
enabled: false
},
vite: {
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
// Makes the build include the code of @astrojs/vue in its output instead of loading the package separately.
// This used to happen automatically with npm. Since we replaced package.json with deno.json it no longer does,
// and the build fails with an error about a "virtual:" import.
noExternal: ['@astrojs/vue']
},
// Sass-related options
css: {
preprocessorOptions: {
Expand Down
20 changes: 20 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"nodeModulesDir": "auto",
"minimumDependencyAge": "P14D",
"tasks": {
"dev": "ASTRO_TELEMETRY_DISABLED=1 ASTRO_DISABLE_UPDATE_CHECK=true deno run --allow-read --allow-write=. --allow-env --allow-sys --allow-ffi --allow-run --allow-net=localhost,127.0.0.1,0.0.0.0,[::] astro dev",
"build": "ASTRO_TELEMETRY_DISABLED=1 deno run --allow-read --allow-write=. --allow-env --allow-sys --allow-ffi --allow-run --allow-net=localhost,127.0.0.1 astro build",
"preview": "ASTRO_TELEMETRY_DISABLED=1 deno run --allow-read --allow-write=. --allow-env --allow-sys --allow-ffi --allow-run --allow-net=localhost,127.0.0.1,0.0.0.0,[::] astro preview"
},
"imports": {
"@astrojs/mdx": "npm:@astrojs/mdx@8.0.2",
"@astrojs/vue": "npm:@astrojs/vue@7.0.3",
"@nanostores/vue": "npm:@nanostores/vue@1.0.1",
"astro": "npm:astro@7.3.5",
"nanostores": "npm:nanostores@1.0.1",
"remove-markdown": "npm:remove-markdown@0.6.2",
"sanitize-html": "npm:sanitize-html@2.17.0",
"sass": "npm:sass@1.89.2",
"vue": "npm:vue@3.5.43"
}
}
Loading