Skip to content

Phase 0: demolition — net −14k lines, one theme, fixed layout#8

Merged
drftstatic merged 8 commits into
mainfrom
claude/phase-0-demolition
Jul 20, 2026
Merged

Phase 0: demolition — net −14k lines, one theme, fixed layout#8
drftstatic merged 8 commits into
mainfrom
claude/phase-0-demolition

Conversation

@drftstatic

Copy link
Copy Markdown
Owner

What

Executes Phase 0 of the modernization PRD (§6 cut list) in 8 checkpointed commits, plus a new platform strategy section in the PRD. Net −16,595 / +2,649 lines (excluding lockfile). Typecheck and both builds pass; app verified live in the browser (search → results → playback all working).

The cuts

  1. 18 dead dependencies — passport/session auth, uppy upload suite, Google Cloud packages; all imported nowhere.
  2. Database-as-cache layer — Postgres/Drizzle/Neon existed only to back in-memory caches; now memory-only LRU with TTL. Never-deployed transcode/HLS/disk-cache routes removed with their routes and client pollers.
  3. Novelty modes — soundboards, easter eggs, ASCII terminal mode, Blondie geometry mode, Hulkster mode.
  4. Layout theater + onboarding — layout demonstrator/hint components and all three welcome/tour flows; framer-motion replaced by a CSS-only shim with the same API.
  5. 27 unused shadcn/ui wrappers + orphan deps (recharts, embla, input-otp, react-day-picker, react-hook-form, 13 radix packages).
  6. Ten themes → one EBN identity — a TypeScript-API codemod folded all 162 brandSkin comparisons and 17 switch dispatches across 25 files; index.css 1,340 → 660 lines.
  7. Floating-panel workspace → fixed three-zone layout — Crate (search) | Signal (player) | Timeline (queue + collapsible effects). store.ts 1,064 → 338 lines; all panel x/y/z-index state gone.
  8. DevicePrompt modal removed (zero-modal onboarding per PRD §5.2).

Platform strategy (new PRD §5.5)

Decision wrapped into this PR: one engine, three shells.

  • Phase 1 builds web-first — the WebGL engine is platform-portable; the zero-install URL stays the free front door.
  • Phase 1.5 wraps in Electron (not Tauri — Tauri's WKWebView kills Web MIDI/WebCodecs, our Chromium-first bets). The shell earns its keep with what browsers physically can't do: Syphon/Spout/NDI output and the offline set cache (in-process Express + real filesystem = gig-safe local playback).
  • Full native rewrite rejected — the Resolume-envy trap.
  • iPad later via Capacitor (Phase 4) over the same engine core; Electron can't run on iPadOS.

Reviewer notes

  • The codemod reformatted the 25 theme-touched files (TS printer output) — diffs there are mostly mechanical.
  • Effects are still the old CSS filters by design; real shader effects are Phase 1 (the WebGL two-deck engine).
  • Center column gets tight below ~1000px viewport width; fine on real screens, will be addressed with the Phase 1 layout polish.
  • AnimatedTransitions.tsx keeps the old API as a CSS shim so 8 consumers didn't churn; it shrinks further as those components are replaced in Phase 1/2.

🤖 Generated with Claude Code

drftstatic and others added 8 commits July 19, 2026 18:10
…rage)

passport/session stack, uppy upload suite, and Google Cloud packages were
imported nowhere in the codebase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…HLS pipeline

Postgres/Drizzle existed only to back the in-memory search/metadata caches;
the transcode, disk-cache, and HLS routes never ran on the Vercel deploy and
had no live client consumers. Caches are now memory-only LRU with TTL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CoreSoundboards, EasterEgg + hook, AsciiModeIndicator, ASCII terminal mode,
Blondie geometry mode, and Hulkster mode. Learning-era chrome per PRD §6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er-motion

Deletes LayoutDemonstrator, ResponsiveLayoutHints(+Simple), breakpoint
indicator/manager, LayoutControls, LayoutAnimationWrapper, DockingGuides,
and all three onboarding flows. AnimatedTransitions is now a CSS-only shim
with the same API, dropping framer-motion from the bundle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only 22 of 48 ui primitives were reachable from app code. Removes recharts,
embla-carousel, input-otp, react-day-picker, vaul, react-resizable-panels,
react-hook-form, and 13 unused radix packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BrandSkin is now 'ebn'. theme-utils returns constants; a TS-API codemod
folded all 162 brandSkin comparisons and 17 switch dispatches across 25
files; ThemeSelector/Switcher/Explanations/BrandSkinToggle deleted;
index.css drops from 1,340 to 660 lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…zone layout

Home is now Crate (search results) | Signal (player + transport) | Timeline
(queue + collapsible Effects/Geometry/Trim/Emergency Mix). Deletes
FloatingPanel(sManager), MainToolbar, ToolsPanel, LiveVideoWindow,
DevicePrompt and all panel-position state; store drops to 338 lines.
Verified live: search, results grid, and playback all work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ron/Capacitor)

Web-first Phase 1; Electron shell in Phase 1.5 for Syphon/Spout/NDI and the
offline set cache; Capacitor for iPad in Phase 4. Native rewrite rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@drftstatic
drftstatic merged commit 81cfa41 into main Jul 20, 2026
1 check failed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request dramatically simplifies the application by consolidating it into a single-identity theme (EBN Hijack) and replacing the complex floating panel workspace with a fixed three-zone layout (Crate, Signal, and Timeline). It removes multiple visual themes, easter eggs, database migrations, and several unused UI components, while also replacing Framer Motion with CSS-only transitions. The code review feedback highlights numerous opportunities to clean up the codebase further by removing unused variables (such as brandSkin) and simplifying redundant helper functions that now always return static values due to the theme consolidation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +9 to +18
const { brandSkin } = useStore();
const handleLuckyDipResults = (results: any) => {
onLuckyDipResults?.(results);
};
const getThemeClasses = () => {
{
return 'bg-gray-900/60 border-lime-500/30';
}
};
return (<div className={`inline-flex items-center gap-2 px-3 py-2 rounded-lg border backdrop-blur-sm ${getThemeClasses()}`}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The brandSkin variable is destructured from useStore() but is no longer used in this component. Additionally, the getThemeClasses function contains a redundant nested block and always returns a static string. We can simplify this by removing the unused variable and function, and inlining the class string directly.

    const handleLuckyDipResults = (results: any) => {
        onLuckyDipResults?.(results);
    };
    return (<div className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border backdrop-blur-sm bg-gray-900/60 border-lime-500/30">

Comment on lines +108 to +113
const getPerformanceBarBg = () => {
{
return 'bg-gray-900';
}
};

// Initial connection check
updateConnection();

// Listen for online/offline events
window.addEventListener('online', updateConnection);
window.addEventListener('offline', updateConnection);

// Listen for connection changes if supported
const connection = (navigator as any).connection;
if (connection) {
connection.addEventListener('change', updateConnection);
}

return () => {
window.removeEventListener('online', updateConnection);
window.removeEventListener('offline', updateConnection);
if (connection) {
connection.removeEventListener('change', updateConnection);
}
};
}, []);

// Real-time clock
useEffect(() => {
const interval = setInterval(() => {
setCurrentTime(new Date());
}, 1000);

return () => clearInterval(interval);
}, []);

// Format time for display
const formatTime = (date: Date) => {
return date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};

// Get connection icon based on type and status
const getConnectionIcon = () => {
if (!isOnline) return WifiOff;

switch (connectionType) {
case 'slow-2g':
case '2g':
case '3g':
return Smartphone;
case '4g':
case '5g':
return Smartphone;
case 'wifi':
return Wifi;
default:
return isOnline ? Wifi : WifiOff;
}
};

const ConnectionIcon = getConnectionIcon();

// Get theme-specific darker background for contrast
const getPerformanceBarBg = () => {
switch (brandSkin) {
case 'ebn':
return 'bg-gray-900';
case 'waffle':
return 'bg-yellow-900';
case 'ozzy':
return 'bg-red-900';
case 'hogan':
return 'bg-gray-900';
case 'dx':
return 'bg-gray-900';
case 'mario':
return 'bg-red-900';
case 'dakota':
return 'bg-gray-900';
case 'blondie':
return 'bg-amber-900';
case 'testcard':
return 'bg-slate-900';
default:
return 'bg-gray-900';
}
};

return (
<div
className={`fixed bottom-6 left-0 right-0 ${getPerformanceBarBg()} ${themeClasses.border} border-t z-40 h-8`}
data-testid="bottom-hud"
>
return (<div className={`fixed bottom-6 left-0 right-0 ${getPerformanceBarBg()} ${themeClasses.border} border-t z-40 h-8`} data-testid="bottom-hud">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The getPerformanceBarBg function contains a redundant nested block and always returns a static string. We can simplify this by removing the function and inlining the background class directly in the JSX.

    return (<div className={`fixed bottom-6 left-0 right-0 bg-gray-900 ${themeClasses.border} border-t z-40 h-8`} data-testid="bottom-hud">

Comment on lines +27 to +28
const { brandSkin } = useStore();
const mode = 'ebn';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The brandSkin variable is destructured from useStore() but is no longer used in this component since the theme mode is hardcoded to 'ebn'. We can remove this unused destructuring.

Suggested change
const { brandSkin } = useStore();
const mode = 'ebn';
const mode = 'ebn';

Comment on lines +36 to +46
const getThemeClasses = () => {
{
return {
accent: 'text-lime-400 border-lime-400',
button: 'bg-lime-600 hover:bg-lime-700 text-white',
recording: 'bg-red-600 hover:bg-red-700 text-white animate-pulse',
badge: 'bg-lime-100 text-lime-800'
};
}
};
const theme = getThemeClasses();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The getThemeClasses function contains a redundant nested block and always returns a static theme configuration object. We can simplify this by defining the theme object directly.

    const theme = {
        accent: 'text-lime-400 border-lime-400',
        button: 'bg-lime-600 hover:bg-lime-700 text-white',
        recording: 'bg-red-600 hover:bg-red-700 text-white animate-pulse',
        badge: 'bg-lime-100 text-lime-800'
    };


return (
<div className="flex items-center space-x-1">
const { searchResults, queueItems, setQueueItems, brandSkin } = useStore();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The brandSkin and queueItems variables are destructured from useStore() but are not used anywhere in this component. We can remove them to clean up the destructuring.

Suggested change
const { searchResults, queueItems, setQueueItems, brandSkin } = useStore();
const { searchResults, setQueueItems } = useStore();

Comment on lines +91 to +96
const { brandSkin } = useStore();
const getThemeClasses = () => {
{
return 'bg-gray-200/30';
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The brandSkin variable is destructured from useStore() but is no longer used in this component. Additionally, the getThemeClasses function contains a redundant nested block. We can simplify this by removing the extra braces.

    const getThemeClasses = () => {
        return 'bg-gray-200/30';
    };


return (
<div className="flex flex-wrap gap-1 sm:gap-2 mb-3">
const { brandSkin, searchState } = useStore();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The brandSkin variable is destructured from useStore() but is no longer used in this component. We can remove this unused destructuring.

Suggested change
const { brandSkin, searchState } = useStore();
const { searchState } = useStore();

Comment on lines +54 to +62
const getThemeClasses = () => {
{
return {
chip: 'bg-lime-900/50 text-lime-400 border-lime-500/30 hover:bg-lime-900/70',
active: 'bg-lime-500/30 text-lime-100'
};
}
};
const theme = getThemeClasses();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The getThemeClasses function contains a redundant nested block and always returns a static theme configuration object. We can simplify this by defining the theme object directly.

    const theme = {
        chip: 'bg-lime-900/50 text-lime-400 border-lime-500/30 hover:bg-lime-900/70',
        active: 'bg-lime-500/30 text-lime-100'
    };

Comment on lines 48 to +49
export function SourceToggles({ onSourcesChange }: SourceTogglesProps) {
const { searchState, setSearchState, brandSkin } = useStore();
const [sources, setSources] = useState<SearchSource[]>(() => {
// Initialize sources with state from store or defaults
const currentSources = searchState.sources || ['prelinger', 'fedflix'];
return DEFAULT_SOURCES.map(source => ({
...source,
enabled: currentSources.includes(source.id)
}));
});

const theme = getThemeClasses(brandSkin);

const getSourceIcon = (sourceId: string) => {
switch (sourceId) {
case 'nasa':
return <Rocket size={12} className="animate-pulse" />;
case 'loc':
return <Building size={12} />;
case 'wikimedia':
return <Camera size={12} />;
case 'prelinger':
return <Film size={12} />;
case 'fedflix':
return <Tv size={12} />;
default:
return <Archive size={12} />;
}
};

const handleSourceToggle = (sourceId: string, checked: boolean) => {
const updatedSources = sources.map(source =>
source.id === sourceId ? { ...source, enabled: checked } : source
);
setSources(updatedSources);

const enabledSources = updatedSources
.filter(source => source.enabled)
.map(source => source.id);

setSearchState({ sources: enabledSources, page: 1 });
onSourcesChange();
};

const enabledCount = sources.filter(s => s.enabled).length;

return (
<div className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg border-2 ${
brandSkin === 'mario' ? 'bg-gradient-to-r from-red-500/20 via-yellow-500/20 to-green-500/20 border-yellow-400/50 shadow-[0_0_20px_rgba(255,215,0,0.3)]' :
brandSkin === 'hogan' ? 'bg-gradient-to-r from-red-600/20 to-yellow-500/20 border-yellow-400/50' :
brandSkin === 'dx' ? 'bg-gradient-to-r from-green-600/20 to-black/40 border-green-500/50' :
brandSkin === 'waffle' ? 'bg-gradient-to-r from-amber-400/20 to-yellow-300/20 border-amber-400/50' :
brandSkin === 'ebn' ? 'bg-gradient-to-r from-lime-500/20 to-green-600/20 border-lime-500/50' :
brandSkin === 'ozzy' ? 'bg-gradient-to-r from-red-900/30 to-purple-900/30 border-red-500/50' :
brandSkin === 'maxheadroom' ? 'bg-gradient-to-r from-cyan-500/20 to-pink-500/20 border-cyan-400/50 animate-pulse' :
brandSkin === 'dakota' ? 'bg-gradient-to-r from-purple-500/20 to-pink-500/20 border-purple-400/50' :
brandSkin === 'blondie' ? 'bg-gradient-to-r from-amber-500/20 to-orange-500/20 border-amber-400/50' :
'bg-gradient-to-r from-gray-700/30 to-gray-800/30 border-gray-500/50'
} backdrop-blur-sm transition-all duration-300 hover:scale-105`}>
const { searchState, setSearchState, brandSkin } = useStore();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The brandSkin variable is destructured from useStore() but is no longer used in this component since the container classes are hardcoded. We can remove this unused destructuring.

Suggested change
export function SourceToggles({ onSourcesChange }: SourceTogglesProps) {
const { searchState, setSearchState, brandSkin } = useStore();
const [sources, setSources] = useState<SearchSource[]>(() => {
// Initialize sources with state from store or defaults
const currentSources = searchState.sources || ['prelinger', 'fedflix'];
return DEFAULT_SOURCES.map(source => ({
...source,
enabled: currentSources.includes(source.id)
}));
});
const theme = getThemeClasses(brandSkin);
const getSourceIcon = (sourceId: string) => {
switch (sourceId) {
case 'nasa':
return <Rocket size={12} className="animate-pulse" />;
case 'loc':
return <Building size={12} />;
case 'wikimedia':
return <Camera size={12} />;
case 'prelinger':
return <Film size={12} />;
case 'fedflix':
return <Tv size={12} />;
default:
return <Archive size={12} />;
}
};
const handleSourceToggle = (sourceId: string, checked: boolean) => {
const updatedSources = sources.map(source =>
source.id === sourceId ? { ...source, enabled: checked } : source
);
setSources(updatedSources);
const enabledSources = updatedSources
.filter(source => source.enabled)
.map(source => source.id);
setSearchState({ sources: enabledSources, page: 1 });
onSourcesChange();
};
const enabledCount = sources.filter(s => s.enabled).length;
return (
<div className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg border-2 ${
brandSkin === 'mario' ? 'bg-gradient-to-r from-red-500/20 via-yellow-500/20 to-green-500/20 border-yellow-400/50 shadow-[0_0_20px_rgba(255,215,0,0.3)]' :
brandSkin === 'hogan' ? 'bg-gradient-to-r from-red-600/20 to-yellow-500/20 border-yellow-400/50' :
brandSkin === 'dx' ? 'bg-gradient-to-r from-green-600/20 to-black/40 border-green-500/50' :
brandSkin === 'waffle' ? 'bg-gradient-to-r from-amber-400/20 to-yellow-300/20 border-amber-400/50' :
brandSkin === 'ebn' ? 'bg-gradient-to-r from-lime-500/20 to-green-600/20 border-lime-500/50' :
brandSkin === 'ozzy' ? 'bg-gradient-to-r from-red-900/30 to-purple-900/30 border-red-500/50' :
brandSkin === 'maxheadroom' ? 'bg-gradient-to-r from-cyan-500/20 to-pink-500/20 border-cyan-400/50 animate-pulse' :
brandSkin === 'dakota' ? 'bg-gradient-to-r from-purple-500/20 to-pink-500/20 border-purple-400/50' :
brandSkin === 'blondie' ? 'bg-gradient-to-r from-amber-500/20 to-orange-500/20 border-amber-400/50' :
'bg-gradient-to-r from-gray-700/30 to-gray-800/30 border-gray-500/50'
} backdrop-blur-sm transition-all duration-300 hover:scale-105`}>
const { searchState, setSearchState, brandSkin } = useStore();
export function SourceToggles({ onSourcesChange }: SourceTogglesProps) {
const { searchState, setSearchState } = useStore();

Comment on lines 5 to +6
export function StatusBar() {
const {
brandSkin,
queueItems,
searchResults,
videoEffects,
isPlaying,
floatingPanelStates
} = useStore();

const [fps, setFps] = useState(30);
const [resolution, setResolution] = useState('1080p');
const [audioLevel, setAudioLevel] = useState(0.6);

// Simulate live data updates
useEffect(() => {
const interval = setInterval(() => {
// Simulate FPS fluctuation (25-60 fps)
setFps(Math.floor(25 + Math.random() * 35));

// Simulate audio levels (VU meter bounce)
setAudioLevel(0.2 + Math.random() * 0.6);
}, 100);

return () => clearInterval(interval);
}, []);

const themeClasses = getThemeClasses(brandSkin);

// Count visible panels
const visiblePanels = Object.values(floatingPanelStates).filter(panel => panel.visible).length;

// Get current FX status
const fxActive = videoEffects.glitchIntensity > 0 ||
videoEffects.chromaticAberration > 0 ||
videoEffects.blur > 0 ||
videoEffects.sepia > 0;

return (
<div
className={`fixed bottom-0 left-0 right-0 h-6 ${themeClasses.bgSecondary} ${themeClasses.border} border-t z-50 flex items-center justify-between px-3 text-xs ${themeClasses.textSecondary}`}
data-testid="status-bar"
>
const { brandSkin, queueItems, searchResults, videoEffects, isPlaying } = useStore();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The brandSkin variable is destructured from useStore() but is no longer used in this component since the status bar classes and text are hardcoded. We can remove this unused destructuring.

Suggested change
export function StatusBar() {
const {
brandSkin,
queueItems,
searchResults,
videoEffects,
isPlaying,
floatingPanelStates
} = useStore();
const [fps, setFps] = useState(30);
const [resolution, setResolution] = useState('1080p');
const [audioLevel, setAudioLevel] = useState(0.6);
// Simulate live data updates
useEffect(() => {
const interval = setInterval(() => {
// Simulate FPS fluctuation (25-60 fps)
setFps(Math.floor(25 + Math.random() * 35));
// Simulate audio levels (VU meter bounce)
setAudioLevel(0.2 + Math.random() * 0.6);
}, 100);
return () => clearInterval(interval);
}, []);
const themeClasses = getThemeClasses(brandSkin);
// Count visible panels
const visiblePanels = Object.values(floatingPanelStates).filter(panel => panel.visible).length;
// Get current FX status
const fxActive = videoEffects.glitchIntensity > 0 ||
videoEffects.chromaticAberration > 0 ||
videoEffects.blur > 0 ||
videoEffects.sepia > 0;
return (
<div
className={`fixed bottom-0 left-0 right-0 h-6 ${themeClasses.bgSecondary} ${themeClasses.border} border-t z-50 flex items-center justify-between px-3 text-xs ${themeClasses.textSecondary}`}
data-testid="status-bar"
>
const { brandSkin, queueItems, searchResults, videoEffects, isPlaying } = useStore();
export function StatusBar() {
const { queueItems, searchResults, videoEffects, isPlaying } = useStore();

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
staticbuffet Ready Ready Preview, Comment Jul 20, 2026 1:18am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant