feat: Mobile improvements and Add selective file staging and improve branch switching

This commit is contained in:
gsxdsm
2026-02-17 15:20:28 -08:00
parent de021f96bf
commit 7fcf3c1e1f
42 changed files with 2706 additions and 256 deletions

View File

@@ -1,113 +1,254 @@
/**
* Bundles all web font packages so they're available
* for use in the font customization settings.
* Font Loading Strategy for Mobile PWA Performance
*
* These fonts are self-hosted with the app, so users don't need
* to have them installed on their system.
* Critical fonts (Zed Sans/Mono - used as Geist fallback) are loaded eagerly.
* All other fonts are lazy-loaded on demand when the user selects them
* in font customization settings. This dramatically reduces initial bundle
* size and speeds up mobile PWA loading.
*
* Font loading is split into:
* 1. Critical path: Zed fonts (default/fallback fonts) - loaded synchronously
* 2. Deferred path: All @fontsource fonts - loaded on-demand or after idle
*/
// Zed Fonts (from zed-industries/zed-fonts)
// Critical: Zed Fonts (default fallback) - loaded immediately
import '@/assets/fonts/zed/zed-fonts.css';
// ============================================
// Sans-serif / UI Fonts (Top 10)
// ============================================
/**
* Registry of lazy-loadable font imports.
* Each font family maps to a function that dynamically imports its CSS files.
* This ensures fonts are only downloaded when actually needed.
*/
type FontLoader = () => Promise<void>;
// Inter - Designed specifically for screens; excellent legibility at small sizes
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
const fontLoaders: Record<string, FontLoader> = {
// Sans-serif / UI Fonts
Inter: async () => {
await Promise.all([
import('@fontsource/inter/400.css'),
import('@fontsource/inter/500.css'),
import('@fontsource/inter/600.css'),
import('@fontsource/inter/700.css'),
]);
},
Roboto: async () => {
await Promise.all([
import('@fontsource/roboto/400.css'),
import('@fontsource/roboto/500.css'),
import('@fontsource/roboto/700.css'),
]);
},
'Open Sans': async () => {
await Promise.all([
import('@fontsource/open-sans/400.css'),
import('@fontsource/open-sans/500.css'),
import('@fontsource/open-sans/600.css'),
import('@fontsource/open-sans/700.css'),
]);
},
Montserrat: async () => {
await Promise.all([
import('@fontsource/montserrat/400.css'),
import('@fontsource/montserrat/500.css'),
import('@fontsource/montserrat/600.css'),
import('@fontsource/montserrat/700.css'),
]);
},
Lato: async () => {
await Promise.all([import('@fontsource/lato/400.css'), import('@fontsource/lato/700.css')]);
},
Poppins: async () => {
await Promise.all([
import('@fontsource/poppins/400.css'),
import('@fontsource/poppins/500.css'),
import('@fontsource/poppins/600.css'),
import('@fontsource/poppins/700.css'),
]);
},
Raleway: async () => {
await Promise.all([
import('@fontsource/raleway/400.css'),
import('@fontsource/raleway/500.css'),
import('@fontsource/raleway/600.css'),
import('@fontsource/raleway/700.css'),
]);
},
'Work Sans': async () => {
await Promise.all([
import('@fontsource/work-sans/400.css'),
import('@fontsource/work-sans/500.css'),
import('@fontsource/work-sans/600.css'),
import('@fontsource/work-sans/700.css'),
]);
},
'Source Sans 3': async () => {
await Promise.all([
import('@fontsource/source-sans-3/400.css'),
import('@fontsource/source-sans-3/500.css'),
import('@fontsource/source-sans-3/600.css'),
import('@fontsource/source-sans-3/700.css'),
]);
},
// Roboto - Highly versatile and clean; the standard for Google-based interfaces
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
// Monospace / Code Fonts
'Fira Code': async () => {
await Promise.all([
import('@fontsource/fira-code/400.css'),
import('@fontsource/fira-code/500.css'),
import('@fontsource/fira-code/600.css'),
import('@fontsource/fira-code/700.css'),
]);
},
'JetBrains Mono': async () => {
await Promise.all([
import('@fontsource/jetbrains-mono/400.css'),
import('@fontsource/jetbrains-mono/500.css'),
import('@fontsource/jetbrains-mono/600.css'),
import('@fontsource/jetbrains-mono/700.css'),
]);
},
'Cascadia Code': async () => {
await Promise.all([
import('@fontsource/cascadia-code/400.css'),
import('@fontsource/cascadia-code/600.css'),
import('@fontsource/cascadia-code/700.css'),
]);
},
Iosevka: async () => {
await Promise.all([
import('@fontsource/iosevka/400.css'),
import('@fontsource/iosevka/500.css'),
import('@fontsource/iosevka/600.css'),
import('@fontsource/iosevka/700.css'),
]);
},
Inconsolata: async () => {
await Promise.all([
import('@fontsource/inconsolata/400.css'),
import('@fontsource/inconsolata/500.css'),
import('@fontsource/inconsolata/600.css'),
import('@fontsource/inconsolata/700.css'),
]);
},
'Source Code Pro': async () => {
await Promise.all([
import('@fontsource/source-code-pro/400.css'),
import('@fontsource/source-code-pro/500.css'),
import('@fontsource/source-code-pro/600.css'),
import('@fontsource/source-code-pro/700.css'),
]);
},
'IBM Plex Mono': async () => {
await Promise.all([
import('@fontsource/ibm-plex-mono/400.css'),
import('@fontsource/ibm-plex-mono/500.css'),
import('@fontsource/ibm-plex-mono/600.css'),
import('@fontsource/ibm-plex-mono/700.css'),
]);
},
};
// Open Sans - Neutral and friendly; optimized for web and mobile readability
import '@fontsource/open-sans/400.css';
import '@fontsource/open-sans/500.css';
import '@fontsource/open-sans/600.css';
import '@fontsource/open-sans/700.css';
// Track which fonts have been loaded to avoid duplicate loading
const loadedFonts = new Set<string>();
// Montserrat - Geometric and modern; best for high-impact titles and branding
import '@fontsource/montserrat/400.css';
import '@fontsource/montserrat/500.css';
import '@fontsource/montserrat/600.css';
import '@fontsource/montserrat/700.css';
/**
* Load a specific font family on demand.
* Returns immediately if the font is already loaded.
* Safe to call multiple times - font will only be loaded once.
*/
export async function loadFont(fontFamily: string): Promise<void> {
// Extract the primary font name from CSS font-family string
// e.g., "'JetBrains Mono', monospace" -> "JetBrains Mono"
const primaryFont = fontFamily
.split(',')[0]
.trim()
.replace(/^['"]|['"]$/g, '');
// Lato - Blends professionalism with warmth; ideal for longer body text
import '@fontsource/lato/400.css';
import '@fontsource/lato/700.css';
if (loadedFonts.has(primaryFont)) return;
// Poppins - Geometric and energetic; popular for modern, friendly brand identities
import '@fontsource/poppins/400.css';
import '@fontsource/poppins/500.css';
import '@fontsource/poppins/600.css';
import '@fontsource/poppins/700.css';
const loader = fontLoaders[primaryFont];
if (loader) {
try {
await loader();
loadedFonts.add(primaryFont);
} catch (error) {
// Font loading failed silently - system fallback fonts will be used
console.warn(`Failed to load font: ${primaryFont}`, error);
}
}
}
// Raleway - Elegant with unique characteristics; great for creative portfolios
import '@fontsource/raleway/400.css';
import '@fontsource/raleway/500.css';
import '@fontsource/raleway/600.css';
import '@fontsource/raleway/700.css';
/**
* Load fonts that the user has configured (from localStorage).
* Called during app initialization to ensure custom fonts are available
* before the first render completes.
*/
export function loadUserFonts(): void {
try {
const stored = localStorage.getItem('automaker-storage');
if (!stored) return;
// Work Sans - Optimized for screen readability; feels clean and contemporary
import '@fontsource/work-sans/400.css';
import '@fontsource/work-sans/500.css';
import '@fontsource/work-sans/600.css';
import '@fontsource/work-sans/700.css';
const data = JSON.parse(stored);
const state = data?.state;
// Source Sans 3 - Adobe's first open-source font; highly functional for complex interfaces
import '@fontsource/source-sans-3/400.css';
import '@fontsource/source-sans-3/500.css';
import '@fontsource/source-sans-3/600.css';
import '@fontsource/source-sans-3/700.css';
// Load globally configured fonts
if (state?.fontFamilySans && state.fontFamilySans !== 'default') {
loadFont(state.fontFamilySans);
}
if (state?.fontFamilyMono && state.fontFamilyMono !== 'default') {
loadFont(state.fontFamilyMono);
}
// ============================================
// Monospace / Code Fonts (Top 10)
// ============================================
// Load current project's font overrides
const currentProject = state?.currentProject;
if (currentProject?.fontSans && currentProject.fontSans !== 'default') {
loadFont(currentProject.fontSans);
}
if (currentProject?.fontMono && currentProject.fontMono !== 'default') {
loadFont(currentProject.fontMono);
}
} catch {
// localStorage not available or parse error - ignore
}
}
// Fira Code - Excellent legibility and stylish ligatures (=>, !=, etc.)
import '@fontsource/fira-code/400.css';
import '@fontsource/fira-code/500.css';
import '@fontsource/fira-code/600.css';
import '@fontsource/fira-code/700.css';
/**
* Preload all available fonts during idle time.
* Called after the app is fully loaded to ensure font previews
* in settings work instantly.
*/
export function preloadAllFonts(): void {
const idleCallback =
typeof requestIdleCallback !== 'undefined'
? requestIdleCallback
: (cb: () => void) => setTimeout(cb, 100);
// JetBrains Mono - Designed by JetBrains for developers, focusing on readability
import '@fontsource/jetbrains-mono/400.css';
import '@fontsource/jetbrains-mono/500.css';
import '@fontsource/jetbrains-mono/600.css';
import '@fontsource/jetbrains-mono/700.css';
// Load fonts in batches during idle periods to avoid blocking
const fontNames = Object.keys(fontLoaders);
let index = 0;
// Cascadia Code - Microsoft's font, popular in Windows Terminal, with ligatures
import '@fontsource/cascadia-code/400.css';
import '@fontsource/cascadia-code/600.css';
import '@fontsource/cascadia-code/700.css';
function loadNextBatch() {
const batchSize = 2; // Load 2 fonts per idle callback
const end = Math.min(index + batchSize, fontNames.length);
// Iosevka - Highly customizable, slender sans-serif/slab-serif font
import '@fontsource/iosevka/400.css';
import '@fontsource/iosevka/500.css';
import '@fontsource/iosevka/600.css';
import '@fontsource/iosevka/700.css';
for (let i = index; i < end; i++) {
const fontName = fontNames[i];
if (!loadedFonts.has(fontName)) {
fontLoaders[fontName]()
.then(() => {
loadedFonts.add(fontName);
})
.catch(() => {
// Silently ignore preload failures
});
}
}
// Inconsolata - Popular, clean, and highly readable choice for screens
import '@fontsource/inconsolata/400.css';
import '@fontsource/inconsolata/500.css';
import '@fontsource/inconsolata/600.css';
import '@fontsource/inconsolata/700.css';
index = end;
if (index < fontNames.length) {
idleCallback(loadNextBatch);
}
}
// Source Code Pro - Adobe's clean, geometric, open-source font
import '@fontsource/source-code-pro/400.css';
import '@fontsource/source-code-pro/500.css';
import '@fontsource/source-code-pro/600.css';
import '@fontsource/source-code-pro/700.css';
// IBM Plex Mono - Clean, modern monospaced font from IBM
import '@fontsource/ibm-plex-mono/400.css';
import '@fontsource/ibm-plex-mono/500.css';
import '@fontsource/ibm-plex-mono/600.css';
import '@fontsource/ibm-plex-mono/700.css';
// Note: Monaco/Menlo are macOS system fonts (not bundled)
// Note: Hack font is not available via @fontsource
idleCallback(loadNextBatch);
}

View File

@@ -384,6 +384,41 @@
font-family: var(--font-sans);
}
/* Fixed viewport for mobile - app-like feel with no scrolling/bouncing */
html,
body {
overflow: hidden;
overscroll-behavior: none;
-webkit-overflow-scrolling: touch;
position: fixed;
width: 100%;
height: 100%;
height: 100dvh;
}
#app {
height: 100%;
height: 100dvh;
overflow: hidden;
overscroll-behavior: none;
}
/* Prevent pull-to-refresh and rubber-band scrolling on mobile */
@supports (-webkit-touch-callout: none) {
body {
/* iOS Safari specific: prevent overscroll bounce */
-webkit-touch-callout: none;
}
}
/* Safe area insets for devices with notches/home indicators (viewport-fit=cover) */
#app {
padding-top: env(safe-area-inset-top, 0px);
padding-bottom: env(safe-area-inset-bottom, 0px);
padding-left: env(safe-area-inset-left, 0px);
padding-right: env(safe-area-inset-right, 0px);
}
/* Apply monospace font to code elements */
code,
pre,