Initial commit - but way too late.
Some checks failed
ci / site (push) Has been cancelled

This commit is contained in:
2026-02-10 00:22:18 -05:00
commit af112a713c
173 changed files with 27667 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-10

View File

@@ -0,0 +1,73 @@
## Context
The site currently tracks a limited set of Umami custom events (CTAs and outbound links). This change expands measurement so every clickable item can be uniquely identified and analyzed in Umami, with a consistent set of event properties across pages.
We need this to understand what users do on the site (what they click, where they go) and to support iteration on acquisition/conversion.
Constraints:
- Tracking must be safe when analytics is disabled or misconfigured (no runtime errors).
- Tracking should not block navigation or degrade performance.
- We should avoid collecting PII; event properties should be categorical/structural (ids, placements, destinations).
## Goals / Non-Goals
**Goals:**
- Define a site-wide click tracking taxonomy for unique identification of click targets.
- Ensure every clickable item (at minimum: links and buttons) emits a Umami custom event with a unique, consistent set of properties.
- Keep the instrumentation low-friction: adding a new clickable should require adding a small, predictable set of data attributes.
- Preserve existing CTA and outbound tracking semantics, but align them with the taxonomy so dashboards can segment consistently.
**Non-Goals:**
- Deep product analytics (funnels, sessions, heatmaps) beyond Umami custom events.
- Tracking form input content or any user-provided personal data.
- Implementing a server-side analytics proxy.
## Decisions
### 1) Centralized click listener with data attributes
**Decision:** Keep a single document-level click handler (event delegation) that inspects the clicked element and its ancestors for tracking metadata (data attributes).
**Rationale:** Minimizes per-component JS and avoids hydration. It is robust for both static and dynamically inserted content and does not require wiring on every component.
**Alternatives considered:**
- Per-component tracking calls: more code duplication and higher chance of missed instrumentation.
- Framework-level router hooks: not applicable for a static-first site and does not cover outbound navigation.
### 2) Taxonomy: stable ids + placement + destination
**Decision:** Standardize on a minimal required property set for every tracked click:
- `action` (event name or action type; e.g., `click`)
- `target_id` (stable identifier for the clickable element; unique within the site)
- `placement` (where it appears; e.g., `nav`, `hero`, `section_header`, `content_card`)
- `target_url` (destination URL for links; optional for buttons)
- Optional: `target_domain`, `source` (youtube/podcast/instagram for content cards), `label`
**Rationale:** This allows segmentation by page region and click target, and provides "where users are going" without needing full URL logging everywhere.
### 3) Event naming strategy
**Decision:** Use a primary generic event name for click tracking (e.g., `click`) and reserve specialized event names (e.g., `cta_click`) only when they provide value or already exist.
**Rationale:** A single event name makes dashboards simpler. Specialized names can be preserved for backwards compatibility, but taxonomy properties must be consistent either way.
### 4) Uniqueness rules
**Decision:** Require every clickable item to define a stable `target_id` that is unique across the site (or unique within a namespace) and deterministic between builds.
**Rationale:** Prevents ambiguous metrics and supports comparing performance over time.
### 5) Safety and performance
**Decision:** Tracking MUST be best-effort:
- Never throw errors if `window.umami` is missing
- Never block navigation (no awaited network calls)
- Avoid attaching large payloads; keep properties small and categorical
## Risks / Trade-offs
- [Over-instrumentation complexity] -> Provide a clear taxonomy and reusable helpers/components for common clickables.
- [Inconsistent ids] -> Document required id rules and add simple lint/test checks later (optional).
- [Umami limitations] -> Keep properties to a small set and ensure they map cleanly to Umami filters.
- [Privacy concerns] -> Prohibit PII in event properties; prefer domains/ids over full URLs where appropriate.

View File

@@ -0,0 +1,27 @@
## Why
We need clear, consistent measurement of what users click on the website and where they go, so we can evaluate acquisition and conversion performance and iterate quickly.
## What Changes
- Standardize click tracking across the entire site so every clickable item emits a custom event with a unique, consistent set of data properties.
- Align event naming and properties with Umami custom event tracking conventions (see Umami "Track events" documentation).
- Ensure tracking works without breaking navigation/UX, and remains safe when analytics is disabled or misconfigured.
## Capabilities
### New Capabilities
- `interaction-tracking-taxonomy`: Define a site-wide event taxonomy for clickable items (required properties, naming conventions, uniqueness rules, and allowed values) and document how new links/buttons must be instrumented.
### Modified Capabilities
- `analytics-umami`: Expand custom event support beyond the current CTA/outbound tracking to cover all clickable items and their unique data properties.
- `conversion-ctas`: Ensure all CTAs conform to the taxonomy (unique identifiers/properties per placement) and remain measurable in Umami with consistent segmentation.
## Impact
- Frontend code: add/standardize data attributes (or equivalent wiring) for all clickable elements (nav links, content cards, CTAs, external links) and update the central tracking hook.
- Analytics: define and maintain an event/property contract; dashboards/filters in Umami will depend on stable names.
- QA: verify in staging that events are emitted as expected and that disabling analytics does not cause client errors.

View File

@@ -0,0 +1,44 @@
## MODIFIED Requirements
### Requirement: Umami pageview tracking
When Umami is enabled by configuration, the site MUST load Umami tracking on all indexable pages and MUST record pageviews.
When Umami is disabled or not configured, the site MUST still function and MUST NOT error in the browser due to missing analytics.
#### Scenario: Umami enabled
- **WHEN** Umami is enabled by configuration
- **THEN** the site includes the Umami script on `/`, `/videos`, `/podcast`, and `/about`
#### Scenario: Umami disabled
- **WHEN** Umami is not configured
- **THEN** the site renders normally and no analytics script is required
### Requirement: Custom event tracking
When Umami is enabled, the site MUST support custom event emission for:
- `cta_click`
- `outbound_click`
- a general click interaction event for all instrumented clickable items (per the site tracking taxonomy)
Each emitted event MUST include enough properties to segment reports by platform and placement when applicable.
All tracked clickable items MUST emit events with a unique, consistent set of data elements as defined by the site tracking taxonomy, including at minimum `target_id` and `placement`.
#### Scenario: Emit outbound click event
- **WHEN** a user clicks a non-CTA outbound link from the homepage
- **THEN** the system emits an `outbound_click` event with a property identifying the destination domain
#### Scenario: Emit general click event for any clickable
- **WHEN** a user clicks an instrumented navigation link
- **THEN** the system emits a click interaction event with `target_id` and `placement`
#### Scenario: Uninstrumented clicks do not break the page
- **WHEN** a user clicks an element with no tracking metadata
- **THEN** the system does not throw and navigation/interaction proceeds normally
### Requirement: Environment configuration
The site MUST support configuration of Umami parameters (at minimum: website ID and script URL) without requiring code changes.
#### Scenario: Configure Umami via environment
- **WHEN** Umami configuration values are provided via environment or config file
- **THEN** the site uses those values to initialize analytics without modifying source code

View File

@@ -0,0 +1,41 @@
## MODIFIED Requirements
### Requirement: Reusable CTA component
The site MUST implement CTAs as a reusable component that can render at least the following actions:
- YouTube subscribe action (linking to the channel)
- Instagram follow action (linking to the profile)
- Podcast listen action (linking to a designated destination)
Each CTA MUST be configurable with:
- `platform` (`youtube`, `instagram`, `podcast`)
- `placement` (e.g., `hero`, `module_header`, `footer`)
- destination `url`
#### Scenario: Rendering a YouTube subscribe CTA
- **WHEN** the homepage includes a CTA with `platform: youtube`
- **THEN** the site renders a visible action that links to the configured YouTube channel destination URL
### Requirement: Trackable outbound links
CTA outbound links MUST support appending UTM parameters so traffic can be attributed in downstream analytics.
#### Scenario: UTM parameters applied
- **WHEN** a CTA is configured with UTM parameters
- **THEN** the rendered outbound link includes the UTM query parameters in its URL
### Requirement: CTA click event emission
CTA clicks MUST emit an analytics event with at least:
- event name `cta_click`
- `platform`
- `placement`
- `target` (destination URL or a stable identifier)
In addition, CTA clicks MUST conform to the site click tracking taxonomy and MUST include a stable, unique identifier (`target_id`) for the CTA instance (so multiple CTAs with the same destination can be measured independently).
#### Scenario: User clicks CTA
- **WHEN** a user clicks an Instagram follow CTA in the hero placement
- **THEN** the system emits a `cta_click` event with `platform=instagram` and `placement=hero`
#### Scenario: Two CTAs to the same destination
- **WHEN** two CTAs link to the same destination but appear in different placements
- **THEN** their emitted events contain different `target_id` values

View File

@@ -0,0 +1,43 @@
## ADDED Requirements
### Requirement: Standard click tracking taxonomy
The system MUST define and follow a standard taxonomy for tracking user interactions (clicks) across the website.
The taxonomy MUST define:
- required event name(s)
- required event properties
- allowed values for categorical properties (where applicable)
- uniqueness rules for identifiers
#### Scenario: Adding a new tracked link
- **WHEN** a new link is added to any page/component
- **THEN** it is instrumented according to the taxonomy (required event name and required properties) without additional bespoke tracking code
### Requirement: Unique identifier for every clickable item
Every clickable item that is tracked MUST have a stable identifier (`target_id`) that is unique across the site (or unique within a documented namespace).
The identifier MUST be deterministic across builds for the same element and placement.
#### Scenario: Two links in different placements
- **WHEN** two links point to the same destination but appear in different placements
- **THEN** their `target_id` values are different so their clicks can be measured independently
### Requirement: Minimum required properties
Every tracked click event MUST include, at minimum:
- `target_id`
- `placement`
For links, the event MUST also include:
- `target_url` (or a stable target identifier that can be mapped to a URL)
#### Scenario: Tracking a content card click
- **WHEN** a user clicks a content card link
- **THEN** the emitted event includes `target_id`, `placement`, and `target_url`
### Requirement: No PII in event properties
The taxonomy MUST prohibit including personally identifiable information (PII) in event names or event properties.
#### Scenario: Tracking includes only categorical metadata
- **WHEN** tracking metadata is defined for a clickable item
- **THEN** it contains only categorical identifiers (ids, placements, domains) and does not include user-provided content

View File

@@ -0,0 +1,24 @@
## 1. Taxonomy Definition
- [x] 1.1 Define the canonical event name(s) and required properties for click tracking (target_id, placement, target_url for links)
- [x] 1.2 Document uniqueness rules and naming conventions for `target_id` (including namespaces for nav/cta/cards)
- [x] 1.3 Add a short "instrumentation checklist" section for developers when adding new clickables
## 2. Core Tracking Implementation
- [x] 2.1 Extend the centralized click handler to emit a general click interaction event for all instrumented clickables
- [x] 2.2 Add support for required taxonomy properties (`target_id`, `placement`, `target_url`) on emitted events
- [x] 2.3 Ensure uninstrumented clicks do not emit events and never throw (analytics disabled safe-path)
- [x] 2.4 Keep existing `cta_click` and `outbound_click` semantics but align emitted properties with the taxonomy
## 3. Instrument All Clickables
- [x] 3.1 Instrument nav links with taxonomy attributes (unique `target_id`, `placement=nav`)
- [x] 3.2 Instrument content cards (newest/high-performing/podcast) with taxonomy attributes (unique ids per source + placement)
- [x] 3.3 Instrument all CTA links to include stable unique `target_id` values per placement
- [x] 3.4 Instrument other outbound links (section headers, buttons) with taxonomy attributes and `outbound_click` where applicable
## 4. Verification
- [x] 4.1 Add/extend tests to validate taxonomy mapping (data attributes -> emitted event payload) for at least nav, CTA, and content-card clicks
- [x] 4.2 Verify in staging Umami that click events show up with expected properties and are segmentable by placement and target_id

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-10

View File

@@ -0,0 +1,148 @@
## Context
This change adds a light-weight, SEO-focused website to grow social discovery and conversions for:
- YouTube channel (`youtube.com/santhoshj`)
- Instagram (`@santhoshjanan`)
- Podcast ("Irregular Mind") on major platforms
The site needs to:
- Be fast and crawlable (SEO-first).
- Surface "latest" content and "high-performing" videos prominently on the home page.
- Provide clear CTAs to convert site visitors into followers/subscribers/listeners.
- Provide analytics (target: Umami) and event tracking to measure progress against 10% month-over-month goals.
Constraints and unknowns:
- Current repo appears to contain only OpenSpec artifacts (no existing website stack captured here yet).
- Instagram "dynamic" ingestion is constrained by Meta platform rules and API availability; we should plan for a fallback.
- "High-performing" requires a definition (views/likes/comments/watch-time) and may require YouTube Data API access.
## Goals / Non-Goals
**Goals:**
- Ship a static-first website architecture that is very fast, SEO-friendly, and easy to operate.
- Ingest content from YouTube + podcast RSS, and support Instagram via API where feasible or via embed/fallback.
- Provide a normalized content model usable by homepage modules (latest + featured/high-performing).
- Provide reusable CTAs with trackable outbound clicks and conversion measurement via Umami.
- Provide clear, testable SEO outputs: indexable pages, metadata, sitemap/robots, social sharing cards.
**Non-Goals:**
- Building a full CMS/editorial workflow (no admin UI in scope initially).
- Real-time updates (seconds/minutes). Near-real-time is not required; periodic refresh is acceptable.
- Complex personalization/recommendation engines.
- Replacing platform-native analytics; the goal is directional measurement for acquisition/conversion.
## Decisions
### 1) Static-first framework and deployment
**Decision:** Use a static-first site generator (Astro is the default recommendation) and deploy on linode instance over docker container.
**Rationale:** Static output maximizes performance and SEO while minimizing operational complexity. Astro provides great HTML-by-default output and only hydrates where necessary, keeping the "light-weight" premise intact.
**Alternatives considered:**
- Next.js: good ecosystem, but easier to accidentally ship heavier runtime/hydration than needed.
- Pure static HTML/manual: too brittle and hard to keep "dynamic" content updated.
### 2) Content ingestion model (build-time with scheduled refresh)
**Decision:** Fetch and normalize social content at build time (SSG) into a local data cache (JSON) that drives rendering. Refresh content on a schedule (cron) and on-demand (manual trigger).
**Rationale:** Keeps runtime simple and fast; avoids exposing API keys to the browser; avoids rate-limit issues on every pageview.
**Mechanics:**
- A build step runs `fetch-content` to produce a normalized dataset (e.g., `content/cache/*.json`).
- CI triggers rebuild on a schedule (e.g., hourly or daily) and after new uploads (manual trigger).
**Alternatives considered:**
- Runtime serverless fetch: more moving parts, harder caching story, slower page loads, and higher risk of API failures impacting the site.
- Client-side fetch: worst for SEO and exposes third-party dependence to users.
### 3) Sources and fallbacks (YouTube, Instagram, podcast)
**YouTube**
- **Decision:** Prefer YouTube Data API (v3) when available to obtain reliable metadata and performance stats (views/likes) for "high-performing".
- **Fallback:** Use the channel RSS feed to list latest videos if API keys are not available; in this mode, "high-performing" becomes manually curated or based on a static list.
**Instagram**
- **Decision:** Start with an embed-first approach for Instagram posts (oEmbed or platform embeds) and treat API-based ingestion as optional Phase 2.
- **Rationale:** Meta API access is frequently the longest pole (app review, token refresh, account type constraints). Embeds still allow reuse of posts without deep integration.
**Podcast**
- **Decision:** Use the podcast RSS feed as the canonical source; parse episodes into the normalized model. Link out to platform destinations (Apple/Spotify/etc).
### 4) Normalized content model and ranking
**Decision:** Define a unified content schema for the site (regardless of source), e.g.:
- `id`, `source` (`youtube|instagram|podcast`), `url`
- `title`, `description`, `publishedAt`
- `thumbnail`, `tags/categories`
- `metrics` (optional: `views`, `likes`, `comments`, `duration`)
- `featured` (manual override)
**High-performing definition:**
- Default: YouTube videos ranked by `views` (optionally recency-weighted), with a manual override list for editorial selection.
- Instagram and podcast "high-performing" starts as manual until reliable metrics are available.
### 5) Information architecture and SEO surface
**Decision:** Create a small set of indexable pages with stable URLs:
- `/` home (latest + featured/high-performing + channel highlights + CTAs)
- `/videos` (YouTube list/detail, or list only initially)
- `/podcast` (episode list)
- `/about` (channel/personal bio, links)
**SEO outputs:**
- Per-page metadata (title/description), canonical URLs
- Open Graph + Twitter cards
- `sitemap.xml` and `robots.txt`
- Structured data (`JSON-LD`) for Video/Podcast where appropriate
### 6) Conversion CTAs and event tracking
**Decision:** Implement CTAs as a reusable component that:
- Renders platform-specific primary actions (YouTube subscribe, Instagram follow, podcast listen)
- Uses outbound links that include UTM parameters
- Emits Umami events for `cta_click` with dimensions like `platform`, `placement`, `target`
### 7) Analytics (Umami) and measurement plan
**Decision:** Use Umami for pageviews + custom events.
**Events (initial):**
- `cta_click` (platform, placement)
- `outbound_click` (domain, placement)
- Optional: `video_play` / `episode_play` if embeds support reliable hooks (may be limited)
**Reporting targets:**
- Traffic by source (SEO vs social referrers)
- CTA click-through rate by placement
- Growth correlation: site referrals to channel follows (approximate; platform attribution is imperfect)
## Risks / Trade-offs
- [Instagram API complexity] → Start embed-first; treat API ingestion as optional and document requirements for later.
- [YouTube API keys / quotas] → Cache results; minimize calls; RSS fallback; allow manual "featured" list.
- [“High-performing” ambiguity] → Specify a clear initial metric (views) and allow manual overrides; refine after baseline data.
- [Stale content due to build-time fetch] → Scheduled rebuilds; manual trigger; show "Last updated" timestamp.
- [SEO requires real content depth] → Add dedicated indexable pages (videos/podcast/about) and ensure metadata + structured data are correct.
- [Analytics privacy expectations] → Keep tracking minimal; avoid PII; document what is tracked.
## Migration Plan
1. Implement the static-first site skeleton and deploy a minimal version (home + about + analytics).
2. Add ingestion for podcast RSS and YouTube (RSS first, upgrade to API when keys are available).
3. Add homepage modules (latest + featured/high-performing) and CTA placements.
4. Add SEO hardening (sitemap/robots/canonicals/structured data) and validate indexing.
5. Add Instagram embed module (and/or API ingestion if chosen).
6. Iterate on ranking and content presentation based on Umami data.
Rollback:
- Revert deploy to previous static build; ingestion failures should fail closed (serve last good cached dataset).
## Open Questions
- Hosting target: Cloudflare Pages vs Netlify vs Vercel (affects cron/rebuild approach and any serverless options).
- Do you want a custom domain (and which), and should the site target a personal brand name vs channel name?
- Is a YouTube Data API key available, or should we start with RSS-only for v1?
- Should Instagram be embed-only for v1, or do you want to pursue API ingestion (which may require a business account/app setup)?
- Preferred measurement: which CTAs matter most (YouTube subs vs Instagram follows vs podcast listens) and where should they be placed (hero, sticky, end of sections)?

View File

@@ -0,0 +1,30 @@
## Why
Grow channel discovery and conversions by giving search engines a fast, crawlable home for SanthoshJ content (YouTube, Instagram, podcast) with clear next actions. This is needed now to drive at least 10% month-over-month growth in both followers and engagement across platforms.
## What Changes
- Add a light-weight, SEO-focused website that aggregates and showcases content across YouTube, Instagram, and the Irregular Mind podcast.
- Dynamically display newest content and highlight high-performing videos prominently on the home page.
- Add prominent conversion CTAs (subscribe/follow/listen) throughout the site to drive measurable conversions from site visits.
- Add analytics instrumentation (target: Umami) to measure traffic sources, content performance, and conversion events.
## Capabilities
### New Capabilities
- `social-content-aggregation`: Pull and normalize content metadata from YouTube, Instagram, and the podcast feed so the website can render "latest" and "featured" content.
- `homepage-content-modules`: Define and render homepage sections for newest content, high-performing videos, and channel/podcast highlights (with graceful empty/loading states).
- `seo-content-surface`: Provide SEO fundamentals (metadata, indexable pages, sitemaps) so content can be discovered via search and shared cleanly on social.
- `conversion-ctas`: Provide reusable, configurable CTAs (YouTube subscribe, Instagram follow, podcast listen) and trackable outbound links.
- `analytics-umami`: Instrument pageviews and key events (CTA clicks, outbound conversions) and support configuration for Umami.
### Modified Capabilities
<!-- None (no existing specs yet) -->
## Impact
- Website codebase: new pages/components for homepage, content listings, and SEO metadata; additional build/runtime configuration to support dynamic content ingestion.
- External integrations: YouTube (Data API or feeds), Instagram (embed/Graph/API constraints), podcast RSS feed parsing; may require API keys and rate-limit handling.
- Analytics/deployment: add Umami script/config and define event taxonomy; ensure privacy expectations and consistent environments (dev/staging/prod).

View File

@@ -0,0 +1,33 @@
## ADDED Requirements
### Requirement: Umami pageview tracking
When Umami is enabled by configuration, the site MUST load Umami tracking on all indexable pages and MUST record pageviews.
When Umami is disabled or not configured, the site MUST still function and MUST NOT error in the browser due to missing analytics.
#### Scenario: Umami enabled
- **WHEN** Umami is enabled by configuration
- **THEN** the site includes the Umami script on `/`, `/videos`, `/podcast`, and `/about`
#### Scenario: Umami disabled
- **WHEN** Umami is not configured
- **THEN** the site renders normally and no analytics script is required
### Requirement: Custom event tracking
When Umami is enabled, the site MUST support custom event emission for:
- `cta_click`
- `outbound_click`
Each emitted event MUST include enough properties to segment reports by platform and placement when applicable.
#### Scenario: Emit outbound click event
- **WHEN** a user clicks a non-CTA outbound link from the homepage
- **THEN** the system emits an `outbound_click` event with a property identifying the destination domain
### Requirement: Environment configuration
The site MUST support configuration of Umami parameters (at minimum: website ID and script URL) without requiring code changes.
#### Scenario: Configure Umami via environment
- **WHEN** Umami configuration values are provided via environment or config file
- **THEN** the site uses those values to initialize analytics without modifying source code

View File

@@ -0,0 +1,35 @@
## ADDED Requirements
### Requirement: Reusable CTA component
The site MUST implement CTAs as a reusable component that can render at least the following actions:
- YouTube subscribe action (linking to the channel)
- Instagram follow action (linking to the profile)
- Podcast listen action (linking to a designated destination)
Each CTA MUST be configurable with:
- `platform` (`youtube`, `instagram`, `podcast`)
- `placement` (e.g., `hero`, `module_header`, `footer`)
- destination `url`
#### Scenario: Rendering a YouTube subscribe CTA
- **WHEN** the homepage includes a CTA with `platform: youtube`
- **THEN** the site renders a visible action that links to the configured YouTube channel destination URL
### Requirement: Trackable outbound links
CTA outbound links MUST support appending UTM parameters so traffic can be attributed in downstream analytics.
#### Scenario: UTM parameters applied
- **WHEN** a CTA is configured with UTM parameters
- **THEN** the rendered outbound link includes the UTM query parameters in its URL
### Requirement: CTA click event emission
CTA clicks MUST emit an analytics event with at least:
- event name `cta_click`
- `platform`
- `placement`
- `target` (destination URL or a stable identifier)
#### Scenario: User clicks CTA
- **WHEN** a user clicks an Instagram follow CTA in the hero placement
- **THEN** the system emits a `cta_click` event with `platform=instagram` and `placement=hero`

View File

@@ -0,0 +1,44 @@
## ADDED Requirements
### Requirement: Homepage modules and ordering
The homepage MUST render distinct content modules that include:
- newest content
- high-performing videos
- channel/podcast highlights
- prominent conversion CTAs
The homepage MUST render modules in a deterministic order configured by the site (not dependent on network timing).
#### Scenario: Homepage render with available data
- **WHEN** content data is available in the cache
- **THEN** the homepage renders the newest module and the high-performing videos module in the configured order
### Requirement: Newest content module
The system MUST compute a "newest" feed across sources by sorting normalized items by `publishedAt` descending.
The system MUST support filtering and limiting the number of items displayed for the newest module.
#### Scenario: Mixed-source newest list
- **WHEN** the cached dataset contains YouTube, Instagram, and podcast items with different publish dates
- **THEN** the newest module lists items ordered by `publishedAt` descending regardless of source
### Requirement: High-performing YouTube videos module
When `metrics.views` is available for YouTube items, the system MUST compute "high-performing" videos by ranking videos by `metrics.views` (descending) with an optional manual override list.
When `metrics.views` is not available, the system MUST render the high-performing module using a manual curated list and MUST NOT fail the page render.
#### Scenario: Views-based ranking available
- **WHEN** YouTube items include `metrics.views`
- **THEN** the high-performing module shows videos ranked by views, unless a manual override list is configured
#### Scenario: Views-based ranking unavailable
- **WHEN** YouTube items do not include `metrics.views`
- **THEN** the high-performing module renders using a manual curated list and the page still loads successfully
### Requirement: Graceful empty and error states
If a module has no content to display, the homepage MUST render a non-broken empty state for that module and MUST still render the rest of the page.
#### Scenario: No Instagram items available
- **WHEN** the cached dataset contains no Instagram items
- **THEN** the Instagram-related module renders an empty state and the homepage still renders other modules

View File

@@ -0,0 +1,48 @@
## ADDED Requirements
### Requirement: Indexable pages
The site MUST provide indexable HTML pages for:
- home (`/`)
- videos (`/videos`)
- podcast (`/podcast`)
- about (`/about`)
These pages MUST be server-rendered or statically generated HTML suitable for search engine crawling (not client-rendered only).
#### Scenario: Crawling the home page
- **WHEN** a crawler requests `/`
- **THEN** the server returns an HTML document containing the homepage content modules and metadata
### Requirement: Metadata and canonical URLs
Each indexable page MUST define:
- a document title
- a meta description
- a canonical URL
#### Scenario: Page metadata is present
- **WHEN** a crawler requests `/videos`
- **THEN** the HTML contains a `<title>`, a meta description, and a canonical URL for `/videos`
### Requirement: Social sharing cards
The site MUST provide Open Graph and Twitter card metadata for indexable pages so shared links render a preview.
#### Scenario: Sharing the home page
- **WHEN** a social crawler requests `/`
- **THEN** the response includes Open Graph and Twitter card tags with a title, description, and image when available
### Requirement: Sitemap and robots
The site MUST provide:
- `sitemap.xml` enumerating indexable pages
- `robots.txt` that allows indexing of indexable pages
#### Scenario: Sitemap is available
- **WHEN** a crawler requests `/sitemap.xml`
- **THEN** the server returns an XML sitemap listing `/`, `/videos`, `/podcast`, and `/about`
### Requirement: Structured data
The site MUST support structured data (JSON-LD) for Video and Podcast content when detail pages exist, and MUST ensure the JSON-LD is valid JSON.
#### Scenario: Video structured data present
- **WHEN** a video detail page exists and is requested
- **THEN** the HTML includes JSON-LD describing the video using a recognized schema type

View File

@@ -0,0 +1,67 @@
## ADDED Requirements
### Requirement: Normalized content items
The system MUST normalize all ingested items (YouTube videos, Instagram posts, podcast episodes) into a single internal schema so the website can render them consistently.
The normalized item MUST include at minimum:
- `id` (stable within its source)
- `source` (`youtube`, `instagram`, or `podcast`)
- `url`
- `title`
- `publishedAt` (ISO-8601)
- `thumbnailUrl` (optional)
#### Scenario: Normalizing a YouTube video
- **WHEN** the system ingests a YouTube video item
- **THEN** it produces a normalized item containing `id`, `source: youtube`, `url`, `title`, and `publishedAt`
#### Scenario: Normalizing a podcast episode
- **WHEN** the system ingests a podcast RSS episode
- **THEN** it produces a normalized item containing `id`, `source: podcast`, `url`, `title`, and `publishedAt`
### Requirement: YouTube ingestion with stats when available
The system MUST support ingesting YouTube videos for channel `youtube.com/santhoshj`.
When a YouTube API key is configured, the system MUST ingest video metadata and MUST ingest view count (and MAY ingest likes/comments if available) so "high-performing" can be computed.
When no YouTube API key is configured, the system MUST still ingest latest videos using a non-authenticated mechanism (for example, channel RSS) but MUST omit performance stats.
#### Scenario: API key configured
- **WHEN** a YouTube API key is configured
- **THEN** the system ingests video metadata and includes `metrics.views` for each ingested video when available from the API
#### Scenario: No API key configured
- **WHEN** no YouTube API key is configured
- **THEN** the system ingests latest videos and does not require `metrics.views` to be present
### Requirement: Podcast RSS ingestion
The system MUST ingest the Irregular Mind podcast RSS feed and produce normalized items representing podcast episodes.
#### Scenario: RSS feed fetch succeeds
- **WHEN** the system fetches the podcast RSS feed successfully
- **THEN** it produces one normalized item per episode with `source: podcast`
### Requirement: Instagram content support via embed-first approach
The system MUST support representing Instagram posts for `@santhoshjanan` in the site content surface.
If API-based ingestion is not configured/available, the system MUST support an embed-first representation where the normalized item contains a `url` to the Instagram post and any additional embed metadata needed by the renderer.
#### Scenario: Embed-first mode
- **WHEN** Instagram API ingestion is not configured
- **THEN** the system provides normalized Instagram items that contain a public post `url` suitable for embedding
### Requirement: Refresh and caching
The system MUST cache the latest successful ingestion output and MUST serve the cached data to the site renderer.
The system MUST support periodic refresh on a schedule (at minimum daily) and MUST support a manual refresh trigger.
On ingestion failure, the system MUST continue serving the most recent cached data.
#### Scenario: Scheduled refresh fails
- **WHEN** a scheduled refresh run fails to fetch one or more sources
- **THEN** the site continues to use the most recent successfully cached dataset
#### Scenario: Manual refresh requested
- **WHEN** a manual refresh is triggered
- **THEN** the system attempts ingestion immediately and updates the cache if ingestion succeeds

View File

@@ -0,0 +1,78 @@
## 1. Repo And Tooling Setup
- [x] 1.1 Choose the site framework and initialize the project skeleton (Astro preferred) with TypeScript support
- [x] 1.2 Add a consistent config approach for secrets and runtime settings (env vars for API keys, Umami settings, base URL)
- [x] 1.3 Add basic quality gates (lint/format, typecheck) and a minimal CI workflow
- [x] 1.4 Add Docker build/run for deployment to the Linode instance (multi-stage build, static output served by nginx or equivalent)
## 2. Content Model And Cache
- [x] 2.1 Define the normalized content item schema (id, source, url, title, publishedAt, thumbnailUrl, optional metrics/views)
- [x] 2.2 Implement a content cache file format and location (e.g., `content/cache/*.json`) plus a "last updated" timestamp
- [x] 2.3 Implement cache read utilities used by page rendering, including source filtering and limit controls
- [x] 2.4 Implement "serve last good cache on failure" behavior in the ingestion workflow (write temp then swap)
## 3. YouTube Ingestion
- [x] 3.1 Implement YouTube ingestion via RSS as the baseline (latest videos, no metrics)
- [x] 3.2 Implement optional YouTube Data API ingestion when `YOUTUBE_API_KEY` is configured (include `metrics.views`)
- [x] 3.3 Implement configurable channel identifier for `youtube.com/santhoshj` (document required values)
- [x] 3.4 Add a small set of local fixtures/tests for parsing and normalization (RSS and API response samples)
## 4. Podcast Ingestion
- [x] 4.1 Add configuration for the Irregular Mind podcast RSS URL
- [x] 4.2 Implement podcast RSS fetch + parse to normalized items (one item per episode)
- [x] 4.3 Ensure episode URLs and publish dates normalize correctly across common RSS date formats
- [x] 4.4 Add fixtures/tests for RSS parsing and normalization
## 5. Instagram Support (Embed-First)
- [x] 5.1 Define the minimal normalized representation for Instagram items in embed-first mode (post URL + optional thumbnail)
- [x] 5.2 Implement a configuration-driven list of Instagram post URLs (or a simple feed source) for v1
- [x] 5.3 Implement the renderer for Instagram embeds with a graceful empty state when no items exist
- [x] 5.4 Document a Phase 2 path for API-based ingestion (constraints, account/app requirements, token refresh)
## 6. Build-Time Refresh And Scheduling
- [x] 6.1 Implement a `fetch-content` script/command that ingests all sources and writes the cache
- [x] 6.2 Add a manual refresh mechanism for ops (document how to run on the Linode host)
- [x] 6.3 Add a scheduled refresh plan (cron on Linode or CI scheduler) and document frequency (daily minimum)
- [x] 6.4 Add logging for ingestion runs (success/failure per source, cache write result)
## 7. Pages And SEO Surface
- [x] 7.1 Implement `/` home page scaffold with deterministic module order
- [x] 7.2 Implement `/videos`, `/podcast`, and `/about` indexable pages (static/SSR HTML, not client-only)
- [x] 7.3 Add per-page metadata: title, description, canonical URL
- [x] 7.4 Add Open Graph + Twitter card tags for indexable pages
- [x] 7.5 Implement `sitemap.xml` and `robots.txt` (include `/`, `/videos`, `/podcast`, `/about`)
- [x] 7.6 Add structured data (JSON-LD) scaffolding for Video/Podcast where detail pages exist (ensure valid JSON)
## 8. Homepage Modules
- [x] 8.1 Implement "newest content" feed (sort by `publishedAt` desc across sources, configurable limits)
- [x] 8.2 Implement "high-performing videos" module using `metrics.views` when present
- [x] 8.3 Implement a manual override/curation list for featured/high-performing videos
- [x] 8.4 Implement graceful empty/error states for each module (page never breaks if a module has no data)
## 9. CTAs And Outbound Tracking
- [x] 9.1 Implement a reusable CTA component with `platform`, `placement`, and destination `url`
- [x] 9.2 Add default destinations for YouTube channel, Instagram profile, and podcast listen target(s)
- [x] 9.3 Add UTM parameter support for CTA outbound links
- [x] 9.4 Define CTA placements on homepage (hero + at least one additional placement) and ensure they are visually prominent
## 10. Umami Analytics
- [x] 10.1 Add Umami configuration (website ID + script URL) via environment/config without code changes per environment
- [x] 10.2 Load Umami on all indexable pages when enabled; ensure site works when disabled
- [x] 10.3 Implement `cta_click` event emission with `platform`, `placement`, and `target`
- [x] 10.4 Implement `outbound_click` event emission with destination domain/identifier
## 11. Verification And Launch
- [x] 11.1 Validate SEO basics locally (view-source contains metadata; sitemap/robots served correctly)
- [x] 11.2 Validate performance basics (no blocking API calls at runtime; pages render from cache)
- [x] 11.3 Validate analytics in a staging environment (events show up in Umami; disabled mode has no errors)
- [x] 11.4 Document deployment steps for the Linode Docker setup (build, run, refresh schedule, rollback)

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-10

View File

@@ -0,0 +1,40 @@
## Context
The site is statically generated (Astro) and deployed behind nginx in Docker. Users can browse to `/videos`, `/podcast`, and `/about`, but direct navigation to these paths is currently resulting in a 404 in the deployed environment.
The static output contains directory-based pages (`/videos/index.html`, `/podcast/index.html`, `/about/index.html`). Some nginx configurations resolve directory indexes only for requests ending with `/` (e.g., `/videos/`) and may 404 for the no-trailing-slash path (`/videos`) unless explicitly handled.
## Goals / Non-Goals
**Goals:**
- Ensure `/videos`, `/podcast`, and `/about` resolve successfully (200) in the nginx/Docker deployment.
- Ensure requests to both the trailing slash and non-trailing slash forms work predictably.
- Keep behavior consistent with the SEO spec (indexable pages remain distinct; no SPA-style fallback masking real 404s).
**Non-Goals:**
- Changing site IA or page content.
- Introducing a client-side router fallback (SPA rewrite) that would hide missing pages.
## Decisions
### 1) Fix nginx static routing for directory index pages
**Decision:** Update nginx `try_files` to include `$uri/index.html` so requests like `/videos` resolve to `/videos/index.html` when present.
**Rationale:** This explicitly supports directory index pages without requiring a redirect, and prevents the common 404 case when the request does not include a trailing slash.
**Alternatives considered:**
- Redirect `/videos` -> `/videos/`: acceptable, but adds an extra hop and needs per-route rules or more complex logic.
- Rewrite all unknown paths to `/index.html`: not acceptable; would mask real 404s and break the semantics of distinct indexable pages.
### 2) Add a small verification step as part of deployment
**Decision:** Validate the deployed container serves `/videos`, `/podcast`, and `/about` as 200 in addition to verifying the build output includes the expected `index.html` files.
**Rationale:** This catches config regressions and ensures the fix applies to the real serving stack.
## Risks / Trade-offs
- [Path collisions] -> Using `$uri/index.html` could cause unexpected resolution if both a file and a directory exist. Mitigation: keep route structure simple and prefer one form.
- [Platform differences] -> Non-nginx deployments might still behave differently. Mitigation: document that the Docker/nginx deploy is canonical and ensure other hosts use equivalent rules.

View File

@@ -0,0 +1,23 @@
## Why
Users navigating to `/videos`, `/podcast`, and `/about` are landing on a 404, which breaks core site navigation and discovery. Fixing this restores expected browsing and improves user experience.
## What Changes
- Fix routing/serving so `/videos`, `/podcast`, and `/about` resolve to the actual generated pages instead of a 404.
- Ensure the fix applies in the deployed static hosting setup (nginx/Docker) and matches local expectations.
## Capabilities
### New Capabilities
<!-- None (bug fix only) -->
### Modified Capabilities
- `seo-content-surface`: Ensure indexable pages `/videos`, `/podcast`, and `/about` resolve correctly in deployed static serving environments (including requests without a trailing slash).
## Impact
- Static server configuration (nginx) and/or build output serving behavior.
- Improves navigation reliability and reduces drop-off from broken links.

View File

@@ -0,0 +1,25 @@
## MODIFIED Requirements
### Requirement: Indexable pages
The site MUST provide indexable HTML pages for:
- home (`/`)
- videos (`/videos`)
- podcast (`/podcast`)
- about (`/about`)
These pages MUST be server-rendered or statically generated HTML suitable for search engine crawling (not client-rendered only).
The deployed static server MUST serve these pages successfully for both trailing-slash and non-trailing-slash requests when a directory-based `index.html` exists (for example, `/videos` and `/videos/` MUST both resolve to the videos page).
#### Scenario: Crawling the home page
- **WHEN** a crawler requests `/`
- **THEN** the server returns an HTML document containing the homepage content modules and metadata
#### Scenario: Direct request without trailing slash
- **WHEN** a user requests `/videos` (no trailing slash)
- **THEN** the server returns the videos page HTML and does not respond with 404
#### Scenario: Direct request with trailing slash
- **WHEN** a user requests `/videos/` (with trailing slash)
- **THEN** the server returns the videos page HTML and does not respond with 404

View File

@@ -0,0 +1,14 @@
## 1. Reproduce And Diagnose
- [x] 1.1 Reproduce the 404 for `/videos`, `/podcast`, and `/about` in the deployed nginx/Docker setup
- [x] 1.2 Confirm the build output includes `site/dist/videos/index.html`, `site/dist/podcast/index.html`, and `site/dist/about/index.html`
## 2. Fix Static Serving
- [x] 2.1 Update nginx config to serve directory index pages for non-trailing-slash requests (e.g., add `$uri/index.html` to `try_files`)
- [x] 2.2 Rebuild the Docker image and restart the container
## 3. Verify
- [x] 3.1 Verify `/videos`, `/podcast`, and `/about` return 200 (both with and without trailing slash)
- [x] 3.2 Verify the fix does not mask real 404s for unknown paths