Imported from ososuna/portfolio (
AGENTS.md). Install upstream withnpx skills add ososuna/portfolio. Copyright stays with the author.
AGENTS.md
Guidelines for AI coding agents working in this repository.
Build & Dev Commands
Package manager: pnpm (pinned via packageManager field in package.json).
pnpm install # Install dependencies
pnpm dev # Start dev server at localhost:4321
pnpm build # Build for production (outputs to ./dist/)
pnpm preview # Preview the production build locally
There are no tests, linters, or formatters configured. No ESLint, Prettier,
editorconfig, vitest, or jest. The only validation is pnpm build — run it to
catch TypeScript errors and broken imports. Always run pnpm build after making
changes to verify nothing is broken.
Architecture
Single-page static Astro portfolio site deployed on Vercel. No SSR, no API endpoints, no client-side framework at runtime. The only client JS is an inline theme toggle script.
- One page:
src/pages/index.astrorenders five sections in order:MainSection→ProfessionalExperience→ProjectsSection→AchievementsSection→AboutMeSection, wrapped inLayout.astro. - Data-driven content: All portfolio content is in JSON files under
src/data/, imported at build time. Edit JSON to update content. - Static output:
astro.config.mjssetsoutput: 'static'with the Vercel adapter and web analytics enabled.
Project Structure
src/
assets/img/ # Optimized images (projects/, achievements/, personal)
components/ # Astro components (sections, cards, buttons, nav)
data/ # JSON content files (experience, projects, achievements)
icons/ # SVG icon components ({Name}Icon.astro)
layouts/Layout.astro # HTML shell, global styles, Navbar
pages/index.astro # Single page composing all sections
env.d.ts # Astro type references
Path Aliases
Always use path aliases for imports. Never use relative paths (./ or ../).
| Alias | Resolves to |
|---|---|
@components/* |
src/components/* |
@layouts/* |
src/layouts/* |
@icons/* |
src/icons/* |
@data/* |
src/data/* |
@assets/* |
src/assets/* |
@img/* |
src/assets/img/* |
Code Style
File & Variable Naming
- Components: PascalCase
.astrofiles —MainSection.astro,ProjectLinkButton.astro - Icons:
{Name}Icon.astro—ReactIcon.astro,GitHubIcon.astro - Data files: lowercase directory matching the JSON filename —
data/projects/projects.json - Variables/functions: camelCase —
projectsData,handleToggleClick,menuOptions - CSS classes: Tailwind utility classes only. Avoid custom class names.
Imports
- Use path aliases exclusively (
@components/,@icons/,@data/,@img/,@layouts/). - Framework imports first (e.g.,
astro:assets), then data, then components, then icons. - Import icons individually — there is no barrel/index file.
TypeScript
- tsconfig extends
astro/tsconfigs/strict— strict mode is active. - Define
interface Propsin the frontmatter for components that accept props:--- interface Props { title: string; link?: string; } const { title, link } = Astro.props; --- - Use
?for optional props. Avoidanytypes where possible. - All logic lives in
.astrofrontmatter — no separate.tsfiles exist.
Component Structure
Astro components follow this order:
---frontmatter (imports, interface, logic)- HTML template
- Optional
<style>block (only when Tailwind classes aren't sufficient) - Optional
<script is:inline>block (only for client-side interactivity)
Props are always destructured from Astro.props in the frontmatter.
Formatting
No formatter is enforced. Follow these dominant patterns:
- Indentation: 2 spaces for
.astrocomponent files and JSON data files. - Semicolons: Always use them in frontmatter code.
- Quotes: Single quotes for JS/TS imports, double quotes for HTML attributes.
- Trailing commas: Include them in multi-line objects and arrays.
- Line length: No hard limit, but Tailwind class strings naturally run long.
Tailwind & Styling
- All styling via Tailwind utility classes — no external CSS files.
- Dark mode:
classstrategy. Toggledarkclass on<html>. Usedark:prefix for dark variants. Dark palette:zinc-950background,whitetext. - Custom breakpoints (below Tailwind defaults):
xss:→ 360pxxs:→ 475px
- Responsive approach: Mobile-first. Base styles target smallest screens,
then
xss:,xs:,md:,lg:for progressively larger viewports. - Global styles (font-face, scroll behavior) live only in
Layout.astrovia<style is:global>.
Icon Components
Icons are pure SVG .astro files with no frontmatter:
<svg {...Astro.props} viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- SVG paths -->
</svg>
Always spread {...Astro.props} on the root <svg> so parents can pass
class, width, height, etc.
Image Handling
Two patterns depending on context:
-
Static import (known single images):
import meImage from '@img/oswaldo-osuna-01.jpeg'; <Image src={meImage} alt="..." class="..." /> -
Dynamic glob (images referenced from JSON data):
const images = import.meta.glob<{ default: ImageMetadata }>('/src/assets/img/projects/*.png'); if (!images[image]) throw new Error(`"${image}" does not exist in glob`); <Image src={images[image]()} alt={title} width="1920" height="1080" />
Always use Astro's <Image> component from astro:assets for optimization.
Content images go in src/assets/img/ (not public/).
Error Handling
This is a static site — errors surface at build time, not runtime. The only error handling pattern is build-time validation for dynamic image imports:
if (!images[image]) throw new Error(`"${image}" does not exist in glob: "..."`);
There are no try/catch blocks, error boundaries, or error pages.
Conditional Rendering & Iteration
- Conditional: Use
&&for "show if truthy", ternary for if/else:{ link && ( <a href={link}>...</a> ) } { link ? <a><Image .../></a> : <Image .../> } - Iteration: Use
.map()with parenthesized return values:{ items.map((item) => ( <Component {...item} /> )) }
Adding Content
- New project: Add entry to
src/data/projects/projects.json. If using a new tag key, add it to thetagsobject inProjectsSection.astroand create a matching icon insrc/icons/. - New experience: Add entry to
src/data/experience/experience.json. If the company is new, add a conditional icon render inTimelineItem.astro. - New achievement: Add entry to
src/data/achievements/achievements.json. - New icon: Create
{Name}Icon.astroinsrc/icons/following the SVG-only pattern with{...Astro.props}spread.
CI/CD
- Deployment: Static build on Vercel via
@astrojs/verceladapter. - GitHub Actions: Two workflows in
.github/workflows/:claude.yml— Claude Code bot triggered by@claudementions in issues/PRs.claude-code-review.yml— Automated Claude Code review on every PR.