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
90 changes: 90 additions & 0 deletions .agents/skills/commit-messages/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
name: commit-messages
description: Use when writing a git commit message for the kasane repo, before invoking `git commit` or `gh pr create`.
---

# Kasane Commit Messages

Project-specific commit style for this repo. Apply to every commit.

## Format

```
<Imperative subject — capitalized, no period, ≤70 chars>

- Short bullet describing one user-facing change
- Another short bullet, with `code` in backticks
- One more if needed
```

## Subject line

- Imperative mood, capitalized first word, no trailing period.
- ≤70 characters. If you can't fit it, split the commit.
- Describe the change, not the files modified.

Real subjects from this repo:

- `Split anime/manga routes and enrich manga view with MangaDex`
- `Rebrand to Kasane and overhaul home + series experience`
- `Add Netlify deploy config`
- `Add react-native-worklets dependency to fix Netlify web build`

## Body

**Favor bullet points.** Skip the body entirely for trivial commits (typo, dep bump).

- One change per bullet. No prose paragraphs.
- Keep each bullet short — single line, ≤80 chars where practical.
- Use **backticks** around any function, file path, variable, route, type, or component: `getAnimeFranchise`, `app/manga/[id]/index.tsx`, `media.type`, `/anime/16498`, `AnimeFranchise`.
- Describe user-facing or semantic changes — not file edits.
- ✅ `Add /manga/[id] route with deep linking` → with backticks: `` `/manga/[id]` ``
- ❌ `Modified app/manga/[id]/index.tsx`

Example body:

```
- Add `/anime/[id]` and `/manga/[id]` typed routes
- Redirect legacy `/series/[id]` to the type-specific route
- Layer MangaDex on AniList for per-volume covers in `MangaDetail`
- Walk `relations.edges` to aggregate `AnimeFranchise.totalTvEpisodes`
```

## Attribution

**Never mention any AI agent or assistant in commit messages.** No `Co-Authored-By: Codex`, no Codex / Cursor / Copilot lines, no "🤖 Generated with …" footers. Commits read as if the human wrote them.

## Invoking `git commit`

Always pass the message via HEREDOC so newlines and backticks survive:

```bash
git commit -m "$(cat <<'EOF'
Subject line here

- First bullet referencing `getMedia`
- Second bullet
EOF
)"
```

## Staging

- Stage files by explicit path; avoid `git add .` / `git add -A`.
- `.Codex/scheduled_tasks.lock` and similar local state are gitignored; don't force-add them.
- Commit `package-lock.json` only when dependencies actually changed.

## Anti-patterns

| Don't | Why |
| ---------------------------------------- | --------------------------------------------- |
| `Co-Authored-By: Codex` (or any agent) | Repo style is human-authored attribution only |
| Past tense ("Added X") | Use imperative ("Add X") |
| Period at end of subject | No trailing punctuation |
| Prose paragraphs in body | Prefer bullets |
| Long wrapped bullet lines | Keep each bullet short and single-line |
| Naked symbol names | Always wrap in backticks: `getMedia` |
| Mechanical file lists ("Modified X.tsx") | Describe the change, not the diff |
| `git add .` / `-A` | Risks staging secrets or local state |
| `--no-verify` | Fix the hook failure instead |
| Amend without explicit user ask | Always create a NEW commit |
90 changes: 90 additions & 0 deletions .claude/skills/commit-messages/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
name: commit-messages
description: Use when writing a git commit message for the kasane repo, before invoking `git commit` or `gh pr create`.
---

# Kasane Commit Messages

Project-specific commit style for this repo. Apply to every commit.

## Format

```
<Imperative subject — capitalized, no period, ≤70 chars>

- Short bullet describing one user-facing change
- Another short bullet, with `code` in backticks
- One more if needed
```

## Subject line

- Imperative mood, capitalized first word, no trailing period.
- ≤70 characters. If you can't fit it, split the commit.
- Describe the change, not the files modified.

Real subjects from this repo:

- `Split anime/manga routes and enrich manga view with MangaDex`
- `Rebrand to Kasane and overhaul home + series experience`
- `Add Netlify deploy config`
- `Add react-native-worklets dependency to fix Netlify web build`

## Body

**Favor bullet points.** Skip the body entirely for trivial commits (typo, dep bump).

- One change per bullet. No prose paragraphs.
- Keep each bullet short — single line, ≤80 chars where practical.
- Use **backticks** around any function, file path, variable, route, type, or component: `getAnimeFranchise`, `app/manga/[id]/index.tsx`, `media.type`, `/anime/16498`, `AnimeFranchise`.
- Describe user-facing or semantic changes — not file edits.
- ✅ `Add /manga/[id] route with deep linking` → with backticks: `` `/manga/[id]` ``
- ❌ `Modified app/manga/[id]/index.tsx`

Example body:

```
- Add `/anime/[id]` and `/manga/[id]` typed routes
- Redirect legacy `/series/[id]` to the type-specific route
- Layer MangaDex on AniList for per-volume covers in `MangaDetail`
- Walk `relations.edges` to aggregate `AnimeFranchise.totalTvEpisodes`
```

## Attribution

**Never mention any AI agent or assistant in commit messages.** No `Co-Authored-By: Claude`, no Codex / Cursor / Copilot lines, no "🤖 Generated with …" footers. Commits read as if the human wrote them.

## Invoking `git commit`

Always pass the message via HEREDOC so newlines and backticks survive:

```bash
git commit -m "$(cat <<'EOF'
Subject line here

- First bullet referencing `getMedia`
- Second bullet
EOF
)"
```

## Staging

- Stage files by explicit path; avoid `git add .` / `git add -A`.
- `.claude/scheduled_tasks.lock` and similar local state are gitignored; don't force-add them.
- Commit `package-lock.json` only when dependencies actually changed.

## Anti-patterns

| Don't | Why |
| ---------------------------------------- | --------------------------------------------- |
| `Co-Authored-By: Claude` (or any agent) | Repo style is human-authored attribution only |
| Past tense ("Added X") | Use imperative ("Add X") |
| Period at end of subject | No trailing punctuation |
| Prose paragraphs in body | Prefer bullets |
| Long wrapped bullet lines | Keep each bullet short and single-line |
| Naked symbol names | Always wrap in backticks: `getMedia` |
| Mechanical file lists ("Modified X.tsx") | Describe the change, not the diff |
| `git add .` / `-A` | Risks staging secrets or local state |
| `--no-verify` | Fix the hook failure instead |
| Amend without explicit user ask | Always create a NEW commit |
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ src-tauri/Cargo.lock
.vscode/
.idea/

# Claude Code local state
.claude/
# Claude Code per-machine state (keep skills/agents/settings tracked)
.claude/scheduled_tasks.lock
.claude/*.lock
.claude/settings.local.json

112 changes: 110 additions & 2 deletions app/anime/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { useMemo, useState } from "react";
import {
ActivityIndicator,
Image,
Pressable,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from "react-native";
import { useLocalSearchParams } from "expo-router";
import { Link, useLocalSearchParams } from "expo-router";
import { useQuery } from "@tanstack/react-query";
import { getMedia } from "@/api/anilist";
import { getAnimeFranchise, getMedia, hasAnimeSequels } from "@/api/anilist";
import { getMangaDexInfoByAniListId } from "@/api/mangadex";
import {
buildSyntheticMapping,
Expand All @@ -22,6 +23,7 @@ import { EpisodeChapterRail } from "@/components/EpisodeChapterRail";
import { Footer } from "@/components/Footer";
import { Paragraph } from "@/components/Paragraph";
import { formatAniListDate } from "@/data/format";
import type { AnimeFranchise } from "@/types";
import { FONT } from "@/theme";

export default function AnimeDetail() {
Expand All @@ -34,6 +36,15 @@ export default function AnimeDetail() {
enabled: !Number.isNaN(mediaId),
});

const sequels = !!media && hasAnimeSequels(media);

const { data: franchise } = useQuery({
queryKey: ["franchise", mediaId],
queryFn: () => getAnimeFranchise(mediaId),
enabled: sequels,
staleTime: 24 * 60 * 60 * 1000,
});

const curatedMapping = useMemo(
() => findMappingByMediaId(mediaId),
[mediaId],
Expand Down Expand Up @@ -114,6 +125,12 @@ export default function AnimeDetail() {
? ` · ${formatAniListDate(media.startDate)}`
: ""}
</Text>
{franchise && franchise.tvSeasonCount > 1 ? (
<Text style={styles.franchiseTotal}>
Franchise total: {franchise.totalTvEpisodes} TV eps across{" "}
{franchise.tvSeasonCount} seasons
</Text>
) : null}
{media.description && (
<Paragraph style={styles.description} numberOfLines={6}>
{media.description.replace(/<[^>]+>/g, "")}
Expand All @@ -122,6 +139,10 @@ export default function AnimeDetail() {
</View>
</View>

{franchise && franchise.seasons.length > 1 ? (
<SeasonsList franchise={franchise} currentId={mediaId} />
) : null}

{mapping ? (
<>
<Text style={styles.sectionTitle}>Episode ↔ Chapter map</Text>
Expand Down Expand Up @@ -161,6 +182,52 @@ export default function AnimeDetail() {
);
}

function SeasonsList({
franchise,
currentId,
}: {
franchise: AnimeFranchise;
currentId: number;
}) {
return (
<View style={styles.seasons}>
<Text style={styles.sectionTitle}>Seasons & entries</Text>
<View style={styles.seasonGrid}>
{franchise.seasons.map((s) => {
const isCurrent = s.id === currentId;
return (
<Link
key={s.id}
href={{ pathname: "/anime/[id]", params: { id: s.id } }}
asChild
>
<Pressable
style={({ hovered, pressed }: any) => [
styles.seasonCard,
isCurrent && styles.seasonCardActive,
{ opacity: pressed ? 0.6 : hovered ? 0.9 : 1 },
]}
>
<Text style={styles.seasonCardTitle} numberOfLines={2}>
{s.title}
</Text>
<Text style={styles.seasonCardMeta}>
{s.format ?? "—"}
{s.episodes ? ` · ${s.episodes} eps` : ""}
{s.year ? ` · ${s.year}` : ""}
</Text>
{isCurrent ? (
<Text style={styles.seasonCardCurrent}>VIEWING</Text>
) : null}
</Pressable>
</Link>
);
})}
</View>
</View>
);
}

function QuickLookup({
mapping,
}: {
Expand Down Expand Up @@ -307,4 +374,45 @@ const styles = StyleSheet.create({
fontFamily: FONT.regular,
},
lookupResult: { color: "#7c5cff", fontSize: 13, fontFamily: FONT.bold },
franchiseTotal: {
color: "#7c5cff",
fontSize: 13,
letterSpacing: -0.2,
fontFamily: FONT.semibold,
paddingTop: 2,
},
seasons: { gap: 10, paddingTop: 4 },
seasonGrid: { flexDirection: "row", flexWrap: "wrap", gap: 10 },
seasonCard: {
width: 220,
padding: 12,
backgroundColor: "#17181b",
borderLeftWidth: 3,
borderLeftColor: "#7c5cff",
gap: 6,
},
seasonCardActive: {
backgroundColor: "#1f1a2e",
borderLeftColor: "#ffd65c",
},
seasonCardTitle: {
color: "#f5f5f5",
fontSize: 14,
lineHeight: 18,
fontFamily: FONT.semibold,
letterSpacing: -0.2,
},
seasonCardMeta: {
color: "#9aa0a6",
fontSize: 11,
letterSpacing: 0.8,
textTransform: "uppercase",
fontFamily: FONT.semibold,
},
seasonCardCurrent: {
color: "#ffd65c",
fontSize: 10,
letterSpacing: 1.4,
fontFamily: FONT.bold,
},
});
Loading
Loading