feat: Add shareable dev patches infrastructure#39
Conversation
Introduces a patch management system for development-only features that shouldn't be committed to the main codebase. ## Patches included: ### dev-login-bypass - Floating dev menu for quick user switching without Google OAuth - Create test users with random emails/names - Toggle admin/premium status per user - Requires: DEV_BYPASS_AUTH=true ### mock-storage - Bypasses R2/S3 when credentials aren't configured - Sample videos from Google Storage (~2MB each) - Thumbnails from Unsplash (tech/coding themed) - Hash-based selection for consistent content per segment - Requires: DEV_MOCK_STORAGE=true ## Usage: - Run `bash .dev/setup.sh` once to add git aliases - Use `git dev-on` / `git dev-off` to toggle all patches - Pre-commit hook prevents accidental commits with patches active 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughThis PR introduces a development-only patch management system that allows developers to toggle feature patches using a YAML configuration file. It includes infrastructure scripts for applying/managing patches, pre-commit hooks, environment variables, and two feature patches: authentication bypass and mock storage support. Changes
Sequence DiagramsequenceDiagram
participant User
participant DevFloatingMenu as DevFloatingMenu<br/>(Frontend)
participant DevAuth as dev-auth<br/>(Server)
participant Session as Session/DB
User->>DevFloatingMenu: Opens menu
DevFloatingMenu->>DevAuth: getDevUsersFn()
DevAuth->>Session: Query dev users
Session-->>DevAuth: Return user list
DevAuth-->>DevFloatingMenu: dev users
DevFloatingMenu-->>User: Display users
User->>DevFloatingMenu: Create new user<br/>(email, name, roles)
DevFloatingMenu->>DevAuth: devLoginFn(user data)
DevAuth->>Session: Create/update user<br/>Set as active user
Session-->>DevAuth: User created
DevAuth-->>DevFloatingMenu: Login success
DevFloatingMenu-->>User: Redirect/refresh UI
alt User switches existing user
User->>DevFloatingMenu: Click user card
DevFloatingMenu->>DevAuth: switchDevUserFn(userId)
DevAuth->>Session: Set active user
Session-->>DevAuth: Success
DevAuth-->>DevFloatingMenu: Updated config
DevFloatingMenu-->>User: UI updates
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (7)
.dev/README.md (1)
31-42: Add a language specifier to the fenced code block.The directory structure code block is missing a language identifier. Use
textorplaintextfor non-code content to satisfy markdown linting rules.🔎 Proposed fix
-``` +```text .dev/ ├── README.md # This file.dev/dev-patch.sh (2)
84-91: Declare and assign separately to avoid masking return values.Per ShellCheck SC2155, combining
localwith command substitution masks the command's return value. Ifget_statefails, the error won't propagate.🔎 Proposed fix
status() { echo "Dev Patches Status:" echo "-------------------" for patch in $(get_all_patches); do - local state=$(get_state "$patch") + local state + state=$(get_state "$patch") [ "$state" = "on" ] && echo -e " ${GREEN}$patch: $state${NC}" || echo -e " $patch: $state" done }
36-44: Reset function silently suppresses all errors.The
|| truepattern inreset_patch_filessuppresses all errors, including genuine failures (corrupted patch, missing git, etc.). Consider logging when the reverse fails.🔎 Proposed improvement
reset_patch_files() { for patch in $(get_all_patches); do local patch_file="$PATCHES_DIR/$patch.patch" if [ -f "$patch_file" ]; then # Use git apply --reverse to cleanly remove patch (handles both modified and new files) - git apply --reverse --ignore-whitespace "$patch_file" 2>/dev/null || true + if ! git apply --reverse --ignore-whitespace "$patch_file" 2>/dev/null; then + # Patch may not be applied or files may have changed - this is expected + : + fi fi done }.dev/patches/mock-storage.patch (1)
803-810: External URLs introduce network dependency for dev mode.The mock storage relies on external URLs (Google Storage, Unsplash). If these services are unavailable or the URLs change, development will break. Consider documenting this dependency or providing a fallback mechanism.
.dev/patches/dev-login-bypass.patch (3)
192-196: Missing dependency in useEffect.The
handleRandomizefunction is called inside the effect but not included in the dependency array. While this works becausehandleRandomizeonly uses a functional state update, it violates the exhaustive-deps rule.🔎 Wrap handleRandomize with useCallback
+ const handleRandomize = useCallback(() => { + setNewUser((prev) => ({ + ...prev, + email: generateRandomEmail(), + name: generateRandomName(), + })); + }, []); - const handleRandomize = () => { - setNewUser((prev) => ({ - ...prev, - email: generateRandomEmail(), - name: generateRandomName(), - })); - }; // Initialize random values when form opens useEffect(() => { if (showCreateForm && !newUser.email) { handleRandomize(); } - }, [showCreateForm, newUser.email]); + }, [showCreateForm, newUser.email, handleRandomize]);
677-694: Consider joining queries for better performance.The current implementation fetches user and profile data with separate queries for each dev user (N queries for N users). While acceptable for dev tooling, this could be optimized with a join operation if the number of dev users grows.
For now, the current approach is clear and maintainable, and performance is not critical for development utilities.
771-787: Consider UX of automatic email/name updates.When users toggle the Admin or Premium checkboxes,
updateUserTypeoverwrites any custom email and name values with preset values. This means if a user has typed a custom email, toggling a checkbox will replace it unexpectedly.For dev tooling, this might be intentional for quick presets. However, you could improve UX by:
- Only updating email/name when they match the previous preset (not custom values)
- Providing separate "Quick Presets" buttons instead of automatic updates
- Adding a tooltip explaining the auto-update behavior
The current implementation works but could surprise users who expect manual input to persist.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
.dev/README.md(1 hunks).dev/dev-patch.sh(1 hunks).dev/hooks/pre-commit-patch-guard.sh(1 hunks).dev/patches.yaml(1 hunks).dev/patches/dev-login-bypass.patch(1 hunks).dev/patches/mock-storage.patch(1 hunks).dev/setup.sh(1 hunks).env.sample(1 hunks).gitattributes(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-19T06:04:43.431Z
Learnt from: CR
Repo: webdevcody/agentic-jumpstart PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T06:04:43.431Z
Learning: Applies to **/routes/admin/**/*.tsx : When making an admin page, protect it using `assertIsAdminFn()` in the `beforeLoad` hook with `createFileRoute`
Applied to files:
.dev/patches/dev-login-bypass.patch
📚 Learning: 2025-12-19T06:04:43.431Z
Learnt from: CR
Repo: webdevcody/agentic-jumpstart PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T06:04:43.431Z
Learning: Applies to **/routes/**/*.tsx : Pages should use the `Page` component and `PageHeader` component when possible
Applied to files:
.dev/patches/dev-login-bypass.patch
📚 Learning: 2025-12-19T06:04:43.431Z
Learnt from: CR
Repo: webdevcody/agentic-jumpstart PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T06:04:43.431Z
Learning: Applies to **/routes/**/*.tsx : Implement error boundaries at the route level using `DefaultCatchBoundary`
Applied to files:
.dev/patches/dev-login-bypass.patch
📚 Learning: 2025-12-19T06:04:43.431Z
Learnt from: CR
Repo: webdevcody/agentic-jumpstart PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T06:04:43.431Z
Learning: Routes are auto-generated to `/src/routeTree.gen.ts` and should never be edited manually
Applied to files:
.dev/patches/dev-login-bypass.patch
🪛 markdownlint-cli2 (0.18.1)
.dev/README.md
31-31: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 Shellcheck (0.11.0)
.dev/dev-patch.sh
[warning] 88-88: Declare and assign separately to avoid masking return values.
(SC2155)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Playwright Tests
🔇 Additional comments (18)
.gitattributes (1)
1-32: LGTM!Comprehensive
.gitattributesconfiguration that correctly enforces LF line endings for text files (critical for shell scripts) and declares binary files appropriately. This ensures cross-platform consistency for the patch system..dev/README.md (1)
1-30: Documentation is well-structured and comprehensive.Clear instructions for setup, usage, and creating new patches. The troubleshooting section with common issues is particularly helpful.
.env.sample (1)
33-41: LGTM!Good approach keeping the dev variables commented out by default, requiring explicit opt-in. Clear documentation references guide developers to the patch system docs.
.dev/patches.yaml (1)
1-6: LGTM!Clean configuration with safe defaults (both patches off). The format works well with the shell-based YAML parsing in
dev-patch.sh..dev/hooks/pre-commit-patch-guard.sh (1)
1-13: LGTM!The hook correctly uses
set -efor fail-fast behavior and resolves the script path properly. The conditional check allows the hook to be installed even if the patch system isn't fully set up yet..dev/setup.sh (1)
1-30: LGTM!Well-structured setup script. Using
--localfor git config is the right choice to scope aliases to this repository. The output feedback listing available commands improves developer discoverability..dev/patches/mock-storage.patch (1)
782-787: ImprovedgetThumbnailKeyhandles edge cases.Good improvement to handle keys that don't end with
.mp4. The fallback appends_thumb.jpgdirectly..dev/patches/dev-login-bypass.patch (11)
1-48: LGTM! Clean integration of dev menu into root route.The parallel loading with
Promise.allis a good optimization, and the defensive default values ensure the dev menu won't render unexpectedly. The conditional rendering based ondevMenuConfig.isEnabledis the correct approach.
49-66: LGTM! Proper bypass with redirect_uri preservation.The early return before the OAuth flow begins is the right approach, and preserving the
redirect_uriparameter ensures users end up at their intended destination after dev login.
67-396: Well-structured dev menu with good UX.The draggable positioning with corner-snapping and localStorage persistence provides a smooth developer experience. The query/mutation integration is clean, and hard reloads after user switches are appropriate for ensuring session state is fully refreshed.
397-477: LGTM! Clean presentational component.Good use of avatar fallback with initials, proper button semantics, and clear visual distinction for the current user. The conditional styling and disabled states are handled correctly.
478-551: LGTM! Simple and effective random generation.The
localhost.testdomain ensures these emails won't conflict with real addresses, and the adjective-noun pattern produces readable test data. Perfect for dev tooling.
576-619: LGTM! Well-designed helper functions.The hash-based DiceBear style selection ensures consistent avatars for each dev user, and the mock GoogleUser structure properly mirrors the real OAuth response with the "dev-" prefix for easy identification.
629-659: Excellent reuse of existing OAuth flow.Creating the mock GoogleUser and routing it through
getAccountByGoogleIdUseCaseandcreateGoogleUserUseCaseensures dev users follow the same code paths as production OAuth, reducing the chance of divergent behavior. The dev-only flag updates (isAdmin, isPremium) are appropriately applied after creation.
700-721: LGTM! Proper validation and security checks.The Zod input validation, env var check, and verification that the target is a dev user (via the "dev-" prefix check) provide appropriate safeguards. This is the correct pattern that should also be used in
devLoginFn.
723-737: LGTM! Clean configuration retrieval.The early return when dev bypass is disabled and the safe handling of the current user lookup are both correct. This provides the necessary data for the root route to conditionally render the dev menu.
789-801: LGTM! Proper form submission and redirect.The hard redirect with
window.location.hrefis necessary to ensure the session cookie is properly set and picked up by the browser. Error handling logs to console, which is appropriate for dev tooling.
803-870: Clean dev login UI with appropriate warnings.The yellow "DEV" badge and description clearly communicate that this is a development-only feature. The form layout is intuitive and the conditional loading state on the submit button provides good feedback.
| set_state() { | ||
| local patch="$1" | ||
| local state="$2" | ||
| sed -i "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE" | ||
| } |
There was a problem hiding this comment.
sed -i is not portable to macOS.
macOS sed requires an empty string argument for in-place editing (sed -i ''). This will fail on macOS with sed: 1: "...": extra characters at the end of command.
🔎 Proposed fix for cross-platform compatibility
set_state() {
local patch="$1"
local state="$2"
- sed -i "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE"
+ if [[ "$OSTYPE" == "darwin"* ]]; then
+ sed -i '' "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE"
+ else
+ sed -i "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE"
+ fi
}Alternatively, use a portable pattern that works on both:
set_state() {
local patch="$1"
local state="$2"
- sed -i "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE"
+ local tmp_file=$(mktemp)
+ sed "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE" > "$tmp_file" && mv "$tmp_file" "$STATE_FILE"
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| set_state() { | |
| local patch="$1" | |
| local state="$2" | |
| sed -i "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE" | |
| } | |
| set_state() { | |
| local patch="$1" | |
| local state="$2" | |
| if [[ "$OSTYPE" == "darwin"* ]]; then | |
| sed -i '' "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE" | |
| else | |
| sed -i "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE" | |
| fi | |
| } |
| set_state() { | |
| local patch="$1" | |
| local state="$2" | |
| sed -i "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE" | |
| } | |
| set_state() { | |
| local patch="$1" | |
| local state="$2" | |
| local tmp_file=$(mktemp) | |
| sed "s/^ $patch: .*/ $patch: $state/" "$STATE_FILE" > "$tmp_file" && mv "$tmp_file" "$STATE_FILE" | |
| } |
🤖 Prompt for AI Agents
In .dev/dev-patch.sh around lines 25 to 29, the use of sed -i is not portable to
macOS; update set_state to perform in-place replacement in a cross-platform way
(for example, call sed with a backup suffix on BSD/OSX and then remove the
backup, or detect GNU vs BSD sed and use the appropriate -i invocation, or write
the modified content to a temp file and mv it over the original). Also ensure
the $patch variable is quoted/escaped when used in the sed pattern so patch
names with special chars or spaces are handled safely.
| case "${1:-}" in | ||
| on) apply_patch "$2" ;; | ||
| off) remove_patch "$2" ;; | ||
| toggle) [ "$(get_state "$2")" = "on" ] && remove_patch "$2" || apply_patch "$2" ;; |
There was a problem hiding this comment.
Missing argument validation for patch commands.
The on, off, and toggle commands don't validate that $2 is provided. If invoked without a patch name, get_state "" and set_state "" will produce unexpected behavior.
🔎 Proposed fix
case "${1:-}" in
- on) apply_patch "$2" ;;
- off) remove_patch "$2" ;;
- toggle) [ "$(get_state "$2")" = "on" ] && remove_patch "$2" || apply_patch "$2" ;;
+ on) [ -z "${2:-}" ] && echo -e "${RED}Usage: dev-patch.sh on <patch-name>${NC}" && exit 1; apply_patch "$2" ;;
+ off) [ -z "${2:-}" ] && echo -e "${RED}Usage: dev-patch.sh off <patch-name>${NC}" && exit 1; remove_patch "$2" ;;
+ toggle) [ -z "${2:-}" ] && echo -e "${RED}Usage: dev-patch.sh toggle <patch-name>${NC}" && exit 1; [ "$(get_state "$2")" = "on" ] && remove_patch "$2" || apply_patch "$2" ;;🤖 Prompt for AI Agents
In .dev/dev-patch.sh around lines 97 to 100, the case branches for on/off/toggle
do not validate that the patch-name argument ($2) is present; add an explicit
check at the start of each branch (or before the case) to ensure "$2" is
non-empty and otherwise print a concise usage/error message to stderr and exit
with non-zero status; adjust the toggle branch so it only calls get_state when
"$2" is provided; keep behavior otherwise identical (call apply_patch "$2" or
remove_patch "$2").
| +export const devLoginFn = createServerFn({ method: "POST" }) | ||
| + .inputValidator((data: DevLoginInput) => data) | ||
| + .handler(async ({ data }) => { |
There was a problem hiding this comment.
Input validation is bypassed.
The .inputValidator((data: DevLoginInput) => data) does not perform any runtime validation—it merely casts the type. If called with invalid data (e.g., missing fields, wrong types), this could cause runtime errors or database failures.
🔎 Use a proper Zod schema for validation
+const devLoginSchema = z.object({
+ email: z.string().email(),
+ name: z.string().min(1),
+ isAdmin: z.boolean(),
+ isPremium: z.boolean(),
+});
+
export const devLoginFn = createServerFn({ method: "POST" })
- .inputValidator((data: DevLoginInput) => data)
+ .inputValidator(devLoginSchema)
.handler(async ({ data }) => {This provides runtime type checking and matches the pattern used in switchDevUserFn (line 702).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| +export const devLoginFn = createServerFn({ method: "POST" }) | |
| + .inputValidator((data: DevLoginInput) => data) | |
| + .handler(async ({ data }) => { | |
| const devLoginSchema = z.object({ | |
| email: z.string().email(), | |
| name: z.string().min(1), | |
| isAdmin: z.boolean(), | |
| isPremium: z.boolean(), | |
| }); | |
| export const devLoginFn = createServerFn({ method: "POST" }) | |
| .inputValidator(devLoginSchema) | |
| .handler(async ({ data }) => { |
🤖 Prompt for AI Agents
.dev/patches/dev-login-bypass.patch around lines 621 to 623: the current
.inputValidator((data: DevLoginInput) => data) bypasses runtime validation;
replace it with a Zod-based validator (the same pattern used by switchDevUserFn
at ~line 702) by defining or importing a DevLogin Zod schema and passing its
parse (or safeParse with error handling) to .inputValidator so incoming data is
runtime-validated before the handler runs.
| + if (process.env.DEV_BYPASS_AUTH !== "true") { | ||
| + throw new Error("Dev login is disabled"); | ||
| + } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "DEV_BYPASS_AUTH" --type ts --type tsx --type jsRepository: webdevcody/agentic-jumpstart
Length of output: 99
🏁 Script executed:
rg "DEV_BYPASS_AUTH" -nRepository: webdevcody/agentic-jumpstart
Length of output: 54
🏁 Script executed:
cat -n .dev/patches/dev-login-bypass.patch | head -100Repository: webdevcody/agentic-jumpstart
Length of output: 4927
🏁 Script executed:
sed -n '620,630p' .dev/patches/dev-login-bypass.patchRepository: webdevcody/agentic-jumpstart
Length of output: 416
🏁 Script executed:
grep -n "DEV_BYPASS_AUTH\|export const dev" .dev/patches/dev-login-bypass.patch | head -30Repository: webdevcody/agentic-jumpstart
Length of output: 523
🏁 Script executed:
find . -name "*.env*" -o -name "wrangler.toml" -o -name ".github" -type d | head -20Repository: webdevcody/agentic-jumpstart
Length of output: 95
🏁 Script executed:
cat .env.sample && echo "---" && ls -la .github/workflows/ 2>/dev/null | head -20Repository: webdevcody/agentic-jumpstart
Length of output: 1656
Confirm DEV_BYPASS_AUTH is not accessible in production deployments.
All dev functions properly gate access with if (process.env.DEV_BYPASS_AUTH !== "true") checks, and the feature is clearly documented in .env.sample as optional for local development only. The environment variable is not in version control. However, verify that:
- Your production environment never sets or inherits
DEV_BYPASS_AUTH - The
.dev/patches/directory and dev-only code paths are excluded from production builds - No deployment pipeline accidentally exposes this configuration variable
🤖 Prompt for AI Agents
.dev/patches/dev-login-bypass.patch around lines 625 to 627: the added dev auth
bypass must never be available in production — verify and lock it down by (1)
auditing all production and CI/CD environment configurations to ensure
DEV_BYPASS_AUTH is not set or inherited, (2) ensuring .dev/patches/ and any
dev-only code paths are excluded from production builds/artifacts and not
packaged/deployed, (3) updating deployment pipeline and secrets management to
remove or block this variable and add a CI/CD check that fails if
DEV_BYPASS_AUTH is present in prod configs, and (4) hardening the runtime guard
so the bypass only runs in non-production environments (e.g., require NODE_ENV
!== 'production' in the same check) so accidental env leakage cannot enable it
in production.
| @@ -1,194 +1,194 @@ | ||
| -import { Link } from "@tanstack/react-router"; | ||
| -import { useContinueSlug } from "~/hooks/use-continue-slug"; | ||
| -import { createServerFn } from "@tanstack/react-start"; | ||
| -import { VideoPlayer } from "~/routes/learn/-components/video-player"; | ||
| -import { useQuery } from "@tanstack/react-query"; | ||
| -import { Play, ShoppingCart } from "lucide-react"; | ||
| -import { getStorage } from "~/utils/storage"; | ||
| -import { getThumbnailKey } from "~/utils/video-transcoding"; | ||
| -import { database } from "~/db"; | ||
| -import { segments, modules } from "~/db/schema"; | ||
| -import { eq } from "drizzle-orm"; | ||
| - | ||
| -const getFirstVideoSegmentFn = createServerFn().handler(async () => { | ||
| - // Get segments ordered by module order, then segment order | ||
| - const result = await database | ||
| - .select({ | ||
| - segment: segments, | ||
| - moduleOrder: modules.order, | ||
| - }) | ||
| - .from(segments) | ||
| - .innerJoin(modules, eq(segments.moduleId, modules.id)) | ||
| - .orderBy(modules.order, segments.order); | ||
| - | ||
| - // Find the first segment that has a video and is not premium | ||
| - // (Landing page should show free preview content) | ||
| - const firstVideoSegment = result | ||
| - .map((row) => row.segment) | ||
| - .find( | ||
| - (segment) => | ||
| - segment.videoKey && !segment.isPremium && !segment.isComingSoon | ||
| - ); | ||
| - | ||
| - // Get thumbnail URL server-side if available | ||
| - let thumbnailUrl: string | null = null; | ||
| - if (firstVideoSegment?.videoKey) { | ||
| - const { storage, type } = getStorage(); | ||
| - if (type === "r2") { | ||
| - const thumbnailKey = | ||
| - firstVideoSegment.thumbnailKey || | ||
| - getThumbnailKey(firstVideoSegment.videoKey); | ||
| - const exists = await storage.exists(thumbnailKey); | ||
| - if (exists) { | ||
| - thumbnailUrl = await storage.getPresignedUrl(thumbnailKey); | ||
| - } | ||
| - } | ||
| - } | ||
| - | ||
| - return { segment: firstVideoSegment, thumbnailUrl }; | ||
| -}); | ||
| - | ||
| -export function HeroSection() { | ||
| - const continueSlug = useContinueSlug(); | ||
| - | ||
| - const { | ||
| - data: firstVideoData, | ||
| - isLoading, | ||
| - error, | ||
| - } = useQuery({ | ||
| - queryKey: ["first-video-segment"], | ||
| - queryFn: () => getFirstVideoSegmentFn(), | ||
| - staleTime: 1000 * 60 * 5, // 5 minutes | ||
| - gcTime: 1000 * 60 * 10, // 10 minutes | ||
| - }); | ||
| - | ||
| - const firstVideoSegment = firstVideoData?.segment; | ||
| - const thumbnailUrl = firstVideoData?.thumbnailUrl; | ||
| - | ||
| - return ( | ||
| - <section className="relative w-full py-12"> | ||
| - {/* Modern AI-themed gradient background */} | ||
| - <div className="absolute inset-0 hero-background-ai"></div> | ||
| - <div className="absolute inset-0 bg-gradient-to-r from-transparent via-theme-500/5 dark:via-theme-950/20 to-transparent"></div> | ||
| - | ||
| - {/* AI circuit pattern overlay */} | ||
| - <div className="absolute inset-0 opacity-5 dark:opacity-10"> | ||
| - <div className="circuit-pattern absolute inset-0"></div> | ||
| - </div> | ||
| - | ||
| - {/* AI-themed floating elements */} | ||
| - <div className="floating-elements"> | ||
| - <div className="floating-element-1"></div> | ||
| - <div className="floating-element-2"></div> | ||
| - <div className="floating-element-3"></div> | ||
| - <div className="floating-element-small top-10 right-10"></div> | ||
| - </div> | ||
| - | ||
| - {/* Content */} | ||
| - <div className="relative z-10 h-full"> | ||
| - <div className="max-w-7xl mx-auto px-6 lg:px-12 h-full"> | ||
| - <div className="flex items-center h-full"> | ||
| - <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 w-full items-center"> | ||
| - {/* Left side - Content */} | ||
| - <div className="hero-content"> | ||
| - {/* Badge */} | ||
| - <div className="inline-flex items-center px-4 py-2 rounded-full bg-theme-50/50 dark:bg-background/20 backdrop-blur-sm border border-theme-200 dark:border-border/50 text-theme-600 dark:text-theme-400 text-sm font-medium mb-8"> | ||
| - <span className="w-2 h-2 bg-theme-500 dark:bg-theme-400 rounded-full mr-2"></span> | ||
| - Agentic Coding Mastery Course | ||
| - </div> | ||
| - | ||
| - <h1 className="text-6xl leading-tight mb-8"> | ||
| - Coding is <span className="text-theme-400">Changing</span>, | ||
| - Master{" "} | ||
| - <span className="text-theme-400">Agentic Coding</span>{" "} | ||
| - </h1> | ||
| - | ||
| - <p className="text-description mb-12 max-w-xl"> | ||
| - Master AI-first development with Cursor IDE, Claude Code CLI, | ||
| - and advanced AI models. Learn how to leverage Opus 4.5, | ||
| - Composer1, GPT-5.1 Codex, and cutting-edge agentic programming | ||
| - techniques to accelerate your development workflow and build | ||
| - applications 10x faster than traditional programming methods. | ||
| - </p> | ||
| - | ||
| - <div className="flex flex-col sm:flex-row gap-4"> | ||
| - <Link | ||
| - to="/purchase" | ||
| - className="inline-flex items-center gap-2 rounded-xl bg-cyan-600 dark:bg-[#22d3ee] px-6 py-2 text-xs font-black text-white dark:text-[#0b101a] shadow-lg shadow-cyan-500/20 transition-all duration-200 hover:brightness-110 hover:shadow-[0_0_15px_rgba(34,211,238,0.4)]" | ||
| - onKeyDown={(e) => { | ||
| - if (e.key === " ") { | ||
| - e.preventDefault(); | ||
| - e.currentTarget.click(); | ||
| - } | ||
| - }} | ||
| - > | ||
| - <ShoppingCart className="w-4 h-4 stroke-[3.5px]" /> | ||
| - Buy Now | ||
| - </Link> | ||
| - <Link | ||
| - to={"/learn/$slug"} | ||
| - params={{ slug: continueSlug }} | ||
| - className="inline-flex items-center gap-2 rounded-xl glass px-5 py-2 text-xs font-bold text-slate-600 dark:text-slate-300 transition-all hover:bg-slate-100 dark:hover:bg-white/10 hover:text-slate-900 dark:hover:text-white" | ||
| - onKeyDown={(e) => { | ||
| - if (e.key === " ") { | ||
| - e.preventDefault(); | ||
| - e.currentTarget.click(); | ||
| - } | ||
| - }} | ||
| - > | ||
| - <Play className="w-3.5 h-3.5 text-cyan-600 dark:text-cyan-400 fill-cyan-600 dark:fill-cyan-400" /> | ||
| - Start Learning | ||
| - </Link> | ||
| - </div> | ||
| - </div> | ||
| - | ||
| - {/* Right side - Video player */} | ||
| - <div className="flex items-center justify-center lg:justify-end"> | ||
| - {isLoading ? ( | ||
| - <div className="w-full max-w-lg lg:max-w-xl aspect-video flex items-center justify-center bg-background/20 backdrop-blur-sm rounded-xl"> | ||
| - <div className="text-sm text-muted-foreground"> | ||
| - Loading video... | ||
| - </div> | ||
| - </div> | ||
| - ) : error ? ( | ||
| - <div className="w-full max-w-lg lg:max-w-xl aspect-video flex items-center justify-center bg-background/20 backdrop-blur-sm rounded-xl"> | ||
| - <div className="text-sm text-muted-foreground"> | ||
| - Unable to load video | ||
| - </div> | ||
| - </div> | ||
| - ) : firstVideoSegment ? ( | ||
| - <div className="w-full max-w-lg lg:max-w-xl"> | ||
| - {/* Video container with glass morphism effect */} | ||
| - <div className="video-container"> | ||
| - <div className="video-wrapper aspect-video relative"> | ||
| - <VideoPlayer | ||
| - segmentId={firstVideoSegment.id} | ||
| - videoKey={firstVideoSegment.videoKey!} | ||
| - initialThumbnailUrl={thumbnailUrl} | ||
| - /> | ||
| - </div> | ||
| - | ||
| - {/* Decorative elements - using theme colors */} | ||
| - <div className="video-decorative-1"></div> | ||
| - <div className="video-decorative-2"></div> | ||
| - </div> | ||
| - </div> | ||
| - ) : ( | ||
| - <div className="w-full max-w-lg lg:max-w-xl aspect-video flex items-center justify-center bg-background/20 backdrop-blur-sm rounded-xl"> | ||
| - <div className="text-sm text-muted-foreground"> | ||
| - No video available | ||
| - </div> | ||
| - </div> | ||
| - )} | ||
| - </div> | ||
| - </div> | ||
| - </div> | ||
| - </div> | ||
| - </div> | ||
| - | ||
| - {/* Bottom gradient fade with theme accent */} | ||
| - <div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-background via-background/80 to-transparent"></div> | ||
| - <div className="section-divider-glow-bottom"></div> | ||
| - </section> | ||
| - ); | ||
| -} | ||
| +import { Link } from "@tanstack/react-router"; | ||
| +import { useContinueSlug } from "~/hooks/use-continue-slug"; | ||
| +import { createServerFn } from "@tanstack/react-start"; | ||
| +import { VideoPlayer } from "~/routes/learn/-components/video-player"; | ||
| +import { useQuery } from "@tanstack/react-query"; | ||
| +import { Play, ShoppingCart } from "lucide-react"; | ||
| +import { getStorage } from "~/utils/storage"; | ||
| +import { getThumbnailKey } from "~/utils/video-transcoding"; | ||
| +import { database } from "~/db"; | ||
| +import { segments, modules } from "~/db/schema"; | ||
| +import { eq } from "drizzle-orm"; | ||
| + | ||
| +const getFirstVideoSegmentFn = createServerFn().handler(async () => { | ||
| + // Get segments ordered by module order, then segment order | ||
| + const result = await database | ||
| + .select({ | ||
| + segment: segments, | ||
| + moduleOrder: modules.order, | ||
| + }) | ||
| + .from(segments) | ||
| + .innerJoin(modules, eq(segments.moduleId, modules.id)) | ||
| + .orderBy(modules.order, segments.order); | ||
| + | ||
| + // Find the first segment that has a video and is not premium | ||
| + // (Landing page should show free preview content) | ||
| + const firstVideoSegment = result | ||
| + .map((row) => row.segment) | ||
| + .find( | ||
| + (segment) => | ||
| + segment.videoKey && !segment.isPremium && !segment.isComingSoon | ||
| + ); | ||
| + | ||
| + // Get thumbnail URL server-side if available | ||
| + let thumbnailUrl: string | null = null; | ||
| + if (firstVideoSegment?.videoKey) { | ||
| + const { storage, type } = getStorage(); | ||
| + if (type === "r2" || type === "mock") { | ||
| + const thumbnailKey = | ||
| + firstVideoSegment.thumbnailKey || | ||
| + getThumbnailKey(firstVideoSegment.videoKey); | ||
| + const exists = await storage.exists(thumbnailKey); | ||
| + if (exists) { | ||
| + thumbnailUrl = await storage.getPresignedUrl(thumbnailKey); | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + return { segment: firstVideoSegment, thumbnailUrl }; | ||
| +}); | ||
| + | ||
| +export function HeroSection() { | ||
| + const continueSlug = useContinueSlug(); | ||
| + | ||
| + const { | ||
| + data: firstVideoData, | ||
| + isLoading, | ||
| + error, | ||
| + } = useQuery({ | ||
| + queryKey: ["first-video-segment"], | ||
| + queryFn: () => getFirstVideoSegmentFn(), | ||
| + staleTime: 1000 * 60 * 5, // 5 minutes | ||
| + gcTime: 1000 * 60 * 10, // 10 minutes | ||
| + }); | ||
| + | ||
| + const firstVideoSegment = firstVideoData?.segment; | ||
| + const thumbnailUrl = firstVideoData?.thumbnailUrl; | ||
| + | ||
| + return ( | ||
| + <section className="relative w-full py-12"> | ||
| + {/* Modern AI-themed gradient background */} | ||
| + <div className="absolute inset-0 hero-background-ai"></div> | ||
| + <div className="absolute inset-0 bg-gradient-to-r from-transparent via-theme-500/5 dark:via-theme-950/20 to-transparent"></div> | ||
| + | ||
| + {/* AI circuit pattern overlay */} | ||
| + <div className="absolute inset-0 opacity-5 dark:opacity-10"> | ||
| + <div className="circuit-pattern absolute inset-0"></div> | ||
| + </div> | ||
| + | ||
| + {/* AI-themed floating elements */} | ||
| + <div className="floating-elements"> | ||
| + <div className="floating-element-1"></div> | ||
| + <div className="floating-element-2"></div> | ||
| + <div className="floating-element-3"></div> | ||
| + <div className="floating-element-small top-10 right-10"></div> | ||
| + </div> | ||
| + | ||
| + {/* Content */} | ||
| + <div className="relative z-10 h-full"> | ||
| + <div className="max-w-7xl mx-auto px-6 lg:px-12 h-full"> | ||
| + <div className="flex items-center h-full"> | ||
| + <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 w-full items-center"> | ||
| + {/* Left side - Content */} | ||
| + <div className="hero-content"> | ||
| + {/* Badge */} | ||
| + <div className="inline-flex items-center px-4 py-2 rounded-full bg-theme-50/50 dark:bg-background/20 backdrop-blur-sm border border-theme-200 dark:border-border/50 text-theme-600 dark:text-theme-400 text-sm font-medium mb-8"> | ||
| + <span className="w-2 h-2 bg-theme-500 dark:bg-theme-400 rounded-full mr-2"></span> | ||
| + Agentic Coding Mastery Course | ||
| + </div> | ||
| + | ||
| + <h1 className="text-6xl leading-tight mb-8"> | ||
| + Coding is <span className="text-theme-400">Changing</span>, | ||
| + Master{" "} | ||
| + <span className="text-theme-400">Agentic Coding</span>{" "} | ||
| + </h1> | ||
| + | ||
| + <p className="text-description mb-12 max-w-xl"> | ||
| + Master AI-first development with Cursor IDE, Claude Code CLI, | ||
| + and advanced AI models. Learn how to leverage Opus 4.5, | ||
| + Composer1, GPT-5.1 Codex, and cutting-edge agentic programming | ||
| + techniques to accelerate your development workflow and build | ||
| + applications 10x faster than traditional programming methods. | ||
| + </p> | ||
| + | ||
| + <div className="flex flex-col sm:flex-row gap-4"> | ||
| + <Link | ||
| + to="/purchase" | ||
| + className="inline-flex items-center gap-2 rounded-xl bg-cyan-600 dark:bg-[#22d3ee] px-6 py-2 text-xs font-black text-white dark:text-[#0b101a] shadow-lg shadow-cyan-500/20 transition-all duration-200 hover:brightness-110 hover:shadow-[0_0_15px_rgba(34,211,238,0.4)]" | ||
| + onKeyDown={(e) => { | ||
| + if (e.key === " ") { | ||
| + e.preventDefault(); | ||
| + e.currentTarget.click(); | ||
| + } | ||
| + }} | ||
| + > | ||
| + <ShoppingCart className="w-4 h-4 stroke-[3.5px]" /> | ||
| + Buy Now | ||
| + </Link> | ||
| + <Link | ||
| + to={"/learn/$slug"} | ||
| + params={{ slug: continueSlug }} | ||
| + className="inline-flex items-center gap-2 rounded-xl glass px-5 py-2 text-xs font-bold text-slate-600 dark:text-slate-300 transition-all hover:bg-slate-100 dark:hover:bg-white/10 hover:text-slate-900 dark:hover:text-white" | ||
| + onKeyDown={(e) => { | ||
| + if (e.key === " ") { | ||
| + e.preventDefault(); | ||
| + e.currentTarget.click(); | ||
| + } | ||
| + }} | ||
| + > | ||
| + <Play className="w-3.5 h-3.5 text-cyan-600 dark:text-cyan-400 fill-cyan-600 dark:fill-cyan-400" /> | ||
| + Start Learning | ||
| + </Link> | ||
| + </div> | ||
| + </div> | ||
| + | ||
| + {/* Right side - Video player */} | ||
| + <div className="flex items-center justify-center lg:justify-end"> | ||
| + {isLoading ? ( | ||
| + <div className="w-full max-w-lg lg:max-w-xl aspect-video flex items-center justify-center bg-background/20 backdrop-blur-sm rounded-xl"> | ||
| + <div className="text-sm text-muted-foreground"> | ||
| + Loading video... | ||
| + </div> | ||
| + </div> | ||
| + ) : error ? ( | ||
| + <div className="w-full max-w-lg lg:max-w-xl aspect-video flex items-center justify-center bg-background/20 backdrop-blur-sm rounded-xl"> | ||
| + <div className="text-sm text-muted-foreground"> | ||
| + Unable to load video | ||
| + </div> | ||
| + </div> | ||
| + ) : firstVideoSegment ? ( | ||
| + <div className="w-full max-w-lg lg:max-w-xl"> | ||
| + {/* Video container with glass morphism effect */} | ||
| + <div className="video-container"> | ||
| + <div className="video-wrapper aspect-video relative"> | ||
| + <VideoPlayer | ||
| + segmentId={firstVideoSegment.id} | ||
| + videoKey={firstVideoSegment.videoKey!} | ||
| + initialThumbnailUrl={thumbnailUrl} | ||
| + /> | ||
| + </div> | ||
| + | ||
| + {/* Decorative elements - using theme colors */} | ||
| + <div className="video-decorative-1"></div> | ||
| + <div className="video-decorative-2"></div> | ||
| + </div> | ||
| + </div> | ||
| + ) : ( | ||
| + <div className="w-full max-w-lg lg:max-w-xl aspect-video flex items-center justify-center bg-background/20 backdrop-blur-sm rounded-xl"> | ||
| + <div className="text-sm text-muted-foreground"> | ||
| + No video available | ||
| + </div> | ||
| + </div> | ||
| + )} | ||
| + </div> | ||
| + </div> | ||
| + </div> | ||
| + </div> | ||
| + </div> | ||
| + | ||
| + {/* Bottom gradient fade with theme accent */} | ||
| + <div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-background via-background/80 to-transparent"></div> | ||
| + <div className="section-divider-glow-bottom"></div> | ||
| + </section> | ||
| + ); | ||
| +} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and read the patch file
if [ -f ".dev/patches/mock-storage.patch" ]; then
echo "=== Patch file found ==="
wc -l .dev/patches/mock-storage.patch
echo ""
echo "=== First 50 lines ==="
head -50 .dev/patches/mock-storage.patch
echo ""
echo "=== Checking for hero.tsx hunk ==="
grep -n "hero.tsx\|+import { Link }\|if (type ===" .dev/patches/mock-storage.patch | head -20
else
echo "Patch file not found at .dev/patches/mock-storage.patch"
# Try to find it
find . -name "*mock-storage*" -type f 2>/dev/null
fiRepository: webdevcody/agentic-jumpstart
Length of output: 2688
🏁 Script executed:
#!/bin/bash
# Look at the exact hunk header for hero.tsx and surrounding lines
echo "=== Hero.tsx hunk header and actual change lines ==="
sed -n '25,100p' .dev/patches/mock-storage.patch
echo ""
echo "=== Counting actual content changes in hero.tsx section ==="
# Extract just the hero.tsx section and analyze it
sed -n '25,/^diff --git/p' .dev/patches/mock-storage.patch | grep -c '^-.*import\|^+.*import'
echo ""
echo "=== Look at video-transcoding.ts hunk ==="
sed -n '1,25p' .dev/patches/mock-storage.patchRepository: webdevcody/agentic-jumpstart
Length of output: 3976
🏁 Script executed:
#!/bin/bash
# Check the actual file to understand the hero.tsx changes
echo "=== Current hero.tsx line count ==="
wc -l src/routes/-components/hero.tsx
echo ""
echo "=== Check for the type === 'r2' change in current file ==="
grep -n "type.*r2.*mock" src/routes/-components/hero.tsx
echo ""
echo "=== Look at git history to see what actually changed ==="
git log --oneline -n 10 2>/dev/null || echo "Not a git repo or git not available"
echo ""
echo "=== Try to find the actual difference using git diff if available ==="
git diff HEAD~1 src/routes/-components/hero.tsx 2>/dev/null | head -100 || echo "Git diff not available"Repository: webdevcody/agentic-jumpstart
Length of output: 387
🏁 Script executed:
#!/bin/bash
# Examine the hero.tsx section more carefully to see what's actually different
echo "=== Extract hero.tsx section from patch ==="
sed -n '/^diff --git a\/src\/routes\/-components\/hero.tsx/,/^diff --git/p' .dev/patches/mock-storage.patch > /tmp/hero_patch.txt
echo "Lines in hero patch section: $(wc -l < /tmp/hero_patch.txt)"
echo ""
echo "=== Count lines that are DIFFERENT (starting with - or +) ==="
grep -c "^-\|^+" /tmp/hero_patch.txt
echo ""
echo "=== Look for the actual functional change ==="
grep -n "type.*===.*mock\|type.*!=.*mock" /tmp/hero_patch.txt
echo ""
echo "=== Check if this is truly all lines or just appears that way ==="
# Count distinct - lines and + lines
minus_count=$(grep -c "^-" /tmp/hero_patch.txt)
plus_count=$(grep -c "^+" /tmp/hero_patch.txt)
echo "Lines with '-' prefix: $minus_count"
echo "Lines with '+' prefix: $plus_count"
echo ""
echo "=== Sample of actual differences (first 10 +/- lines that differ) ==="
# Get lines that have a - and corresponding + that are different
awk 'NR > 5 && /^[-+]/ {print}' /tmp/hero_patch.txt | head -40Repository: webdevcody/agentic-jumpstart
Length of output: 2083
🏁 Script executed:
#!/bin/bash
# Check the actual changes - are they truly identical on lines other than the one with "mock"?
echo "=== Extract paired diff lines and check for actual differences ==="
python3 << 'EOF'
import re
# Read the patch section
with open('/tmp/hero_patch.txt', 'r') as f:
lines = f.readlines()
# Process pairs of - and + lines
i = 0
differences = []
while i < len(lines):
if lines[i].startswith('-') and i+1 < len(lines) and lines[i+1].startswith('+'):
old = lines[i][1:].rstrip()
new = lines[i+1][1:].rstrip()
if old != new:
differences.append((i, old, new))
i += 2
else:
i += 1
print(f"Total paired -/+ lines that actually differ: {len(differences)}")
print("\nDifferences found:")
for idx, old, new in differences:
print(f"Line {idx}:")
print(f" Old: {old[:80]}")
print(f" New: {new[:80]}")
if len(old) > 80 or len(new) > 80:
print(f" (truncated)")
EOF
echo ""
echo "=== Now check MockStorage implementation ==="
if [ -f "src/utils/storage/mock-storage.ts" ]; then
echo "MockStorage file exists"
echo ""
echo "=== MockStorage.exists() implementation ==="
sed -n '/\.exists(/,/^[[:space:]]*}/p' src/utils/storage/mock-storage.ts | head -20
echo ""
echo "=== MockStorage.getStream() implementation ==="
sed -n '/\.getStream(/,/^[[:space:]]*}/p' src/utils/storage/mock-storage.ts | head -20
else
echo "MockStorage file not found - checking patch for implementation"
sed -n '/class MockStorage/,/^+}/p' .dev/patches/mock-storage.patch | tail -100
fiRepository: webdevcody/agentic-jumpstart
Length of output: 3832
Use minimal hunks instead of full-file replacement in hero.tsx patch.
The patch shows all 194 lines of hero.tsx as changed when only one line actually differs. This makes the patch fragile—any formatting or whitespace change to the file breaks the patch. Regenerate with context-aware hunks targeting just the functional change: adding || type === "mock" to the storage type check.
The video-transcoding.ts patch correctly uses surgical hunks with proper line ranges (e.g., @@ -169,7 +169,7 @@), serving as a better reference format.
Additionally, MockStorage.exists() always returns true, which could mask file existence checks elsewhere, and MockStorage.getStream() intentionally throws errors. Both are documented but worth considering for test coverage.
| + async exists(key: string): Promise<boolean> { | ||
| + return true; | ||
| + } |
There was a problem hiding this comment.
exists() always returns true - potential silent failures.
MockStorage.exists() unconditionally returns true, which could mask logic errors in code that depends on actual file existence checks. Consider logging a warning or maintaining a set of "known" keys.
🔎 Proposed improvement
async exists(key: string): Promise<boolean> {
+ // In mock mode, report as existing to allow dev workflows
+ // Note: This may hide issues where code expects real existence checks
+ console.log(`[MockStorage] exists check for: ${key} (always returns true)`);
return true;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| + async exists(key: string): Promise<boolean> { | |
| + return true; | |
| + } | |
| async exists(key: string): Promise<boolean> { | |
| // In mock mode, report as existing to allow dev workflows | |
| // Note: This may hide issues where code expects real existence checks | |
| console.log(`[MockStorage] exists check for: ${key} (always returns true)`); | |
| return true; | |
| } |
🤖 Prompt for AI Agents
In .dev/patches/mock-storage.patch around lines 847 to 849, MockStorage.exists()
currently always returns true which can hide bugs; change it to check a tracked
set of stored keys (e.g., this._keys or this._store) and return whether the key
is present, updating that set when put/delete methods are called; if you want to
preserve older behavior temporarily, emit a warning when exists is called to
indicate the mock may be inaccurate; ensure exists remains async and returns a
boolean based on the internal key set rather than unconditionally true.
| + async getStream( | ||
| + key: string, | ||
| + rangeHeader: string | null | ||
| + ): Promise<StreamFileResponse> { | ||
| + throw new Error( | ||
| + "[MockStorage] getStream not supported. Use getPresignedUrl instead." | ||
| + ); | ||
| + } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find usages of getStream in the codebase to verify they're guarded
echo "=== Searching for getStream usages ==="
rg -n "\.getStream\(" --type ts -C2Repository: webdevcody/agentic-jumpstart
Length of output: 520
🏁 Script executed:
#!/bin/bash
# Read the video.ts file to understand how storage is used
echo "=== Reading video.ts file ==="
cat -n src/routes/api/segments/$segmentId/video.tsRepository: webdevcody/agentic-jumpstart
Length of output: 184
🏁 Script executed:
#!/bin/bash
# Find video.ts file
echo "=== Finding video.ts ==="
fd -type f "video.ts"Repository: webdevcody/agentic-jumpstart
Length of output: 284
🏁 Script executed:
#!/bin/bash
# Find video.ts file
echo "=== Finding video.ts ==="
fd "video.ts"Repository: webdevcody/agentic-jumpstart
Length of output: 140
🏁 Script executed:
#!/bin/bash
# Read the video.ts file
echo "=== Reading video.ts file ==="
cat -n "src/routes/api/segments/\$segmentId/video.ts"Repository: webdevcody/agentic-jumpstart
Length of output: 2121
🏁 Script executed:
#!/bin/bash
# Find and read the storage utility file
echo "=== Finding storage utility ==="
find . -path "*/utils/storage*" -type fRepository: webdevcody/agentic-jumpstart
Length of output: 230
🏁 Script executed:
#!/bin/bash
# Read the storage index file to see the getStorage implementation
echo "=== Reading storage/index.ts ==="
cat -n src/utils/storage/index.tsRepository: webdevcody/agentic-jumpstart
Length of output: 525
🏁 Script executed:
#!/bin/bash
# Search for MockStorage usage
echo "=== Searching for MockStorage usage ==="
rg "MockStorage" --type ts -C 2Repository: webdevcody/agentic-jumpstart
Length of output: 111
🏁 Script executed:
#!/bin/bash
# Search for mock-storage references
echo "=== Searching for mock-storage ==="
rg "mock-storage" --type ts -C 2Repository: webdevcody/agentic-jumpstart
Length of output: 106
🏁 Script executed:
#!/bin/bash
# Look at the patch file itself and its context
echo "=== Reading the mock-storage.patch file ==="
head -100 .dev/patches/mock-storage.patchRepository: webdevcody/agentic-jumpstart
Length of output: 3781
🏁 Script executed:
#!/bin/bash
# Search for the MockStorage class definition in the patch
echo "=== Searching for MockStorage class in patch ==="
grep -n "class MockStorage" .dev/patches/mock-storage.patchRepository: webdevcody/agentic-jumpstart
Length of output: 172
🏁 Script executed:
#!/bin/bash
# Read the MockStorage class definition from the patch file
echo "=== Reading MockStorage class from patch ==="
sed -n '803,900p' .dev/patches/mock-storage.patchRepository: webdevcody/agentic-jumpstart
Length of output: 3465
🏁 Script executed:
#!/bin/bash
# Check if getStorage() is updated in the patch to return MockStorage
echo "=== Checking if getStorage is modified in patch ==="
grep -n "getStorage" .dev/patches/mock-storage.patch | head -20Repository: webdevcody/agentic-jumpstart
Length of output: 518
🏁 Script executed:
#!/bin/bash
# Read the getStorage function modification in the patch
echo "=== Reading getStorage modification ==="
sed -n '430,460p' .dev/patches/mock-storage.patchRepository: webdevcody/agentic-jumpstart
Length of output: 1155
🏁 Script executed:
#!/bin/bash
# Check if video.ts is modified in the patch
echo "=== Searching for video.ts modifications in patch ==="
grep -A 30 "diff --git a/src/routes/api/segments" .dev/patches/mock-storage.patch | head -50Repository: webdevcody/agentic-jumpstart
Length of output: 125
🏁 Script executed:
#!/bin/bash
# Search for getStream calls in the patch file
echo "=== Searching for getStream in patch ==="
grep -n "getStream" .dev/patches/mock-storage.patchRepository: webdevcody/agentic-jumpstart
Length of output: 217
Guard getStream() call against MockStorage usage.
The video.ts route calls storage.getStream() without checking if MockStorage is active. When DEV_MOCK_STORAGE=true, this will throw a runtime error. Add a check for type !== "mock" similar to the guards in video-transcoding.ts and hero.tsx, or implement getStream() support in MockStorage.
🤖 Prompt for AI Agents
.dev/patches/mock-storage.patch around lines 851 to 858: the MockStorage
implementation throws in getStream(), but routes like video.ts call
storage.getStream() unguarded when DEV_MOCK_STORAGE=true, causing runtime
errors; either add a guard in the caller(s) (e.g., in the video.ts route, check
storage.type !== "mock" before calling getStream(), or follow the same pattern
used in video-transcoding.ts and hero.tsx) or implement getStream() behavior in
MockStorage (return a compatible StreamFileResponse or a helpful
no-op/presigned-url alternative). Update callers or MockStorage so getStream is
never invoked on the mock without a safe fallback.
fix: Replace real names with auto-generated aliases for privacy (depends on #39)
Summary
Introduces a patch management system for development-only features that shouldn't be committed to the main codebase. This allows developers to quickly toggle local-only modifications without risk of accidentally pushing them.
Rationale
Lower barrier to entry for contributors:
Faster iteration during development:
Safer experimentation:
Cross-platform consistency:
.gitattributesensures consistent line endings (LF) across Windows/Mac/LinuxWhat's Included
1. Dev Patches System (
.dev/)dev-patch.shsetup.shpatches.yamlhooks/pre-commit-patch-guard.sh2. dev-login-bypass Patch
Floating dev menu for quick user switching without Google OAuth.
Requires:
DEV_BYPASS_AUTH=truein.env3. mock-storage Patch
Bypasses R2/S3 storage using external sample content.
Requires:
DEV_MOCK_STORAGE=truein.env4. Line Ending Normalization (
.gitattributes)Enforces LF line endings to prevent cross-platform issues.
Usage
Test Plan
bash .dev/setup.sh→ git aliases addedgit dev-on→ patches apply with success messagegit dev-off→ patches remove cleanlyDEV_BYPASS_AUTH=true→ dev menu appearsDEV_MOCK_STORAGE=true→ sample videos loadSummary by CodeRabbit
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.