Add standalone /carmode route and nginx documentation

- Add /carmode URL that auto-enters car mode without exit option
- /carmode uses its own API route (/carmode/api/) for isolation
- Hide exit button and disable Escape key on /carmode
- Skip lock screen on dedicated car mode URL
- Replace macOS/Ubuntu docs with nginx configuration guide

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 18:16:33 +00:00
parent 8adbee6e4f
commit 141d2ea6a6
4 changed files with 118 additions and 146 deletions

View File

@@ -1,80 +0,0 @@
News App - macOS Service Management
====================================
Service: com.je.carnews
Port: 5555
Plist: /Library/LaunchDaemons/com.je.carnews.plist
BASIC COMMANDS
--------------
Start the service:
sudo launchctl kickstart system/com.je.carnews
Stop the service:
sudo launchctl kill SIGTERM system/com.je.carnews
Restart the service:
sudo launchctl kickstart -k system/com.je.carnews
Check status:
sudo launchctl print system/com.je.carnews
Check if running:
lsof -i :5555
ADD TO STARTUP (BOOT)
---------------------
1. Copy the plist to LaunchDaemons:
sudo cp /opt/homebrew/var/www/news-app/com.je.carnews.plist /Library/LaunchDaemons/
2. Load the service:
sudo launchctl bootstrap system /Library/LaunchDaemons/com.je.carnews.plist
3. Start it:
sudo launchctl kickstart system/com.je.carnews
The service will now start automatically on boot.
REMOVE FROM STARTUP (BOOT)
--------------------------
1. Stop and unload the service:
sudo launchctl bootout system/com.je.carnews
2. Remove the plist file:
sudo rm /Library/LaunchDaemons/com.je.carnews.plist
The service will no longer start on boot.
VIEW LOGS
---------
Output log:
tail -f /opt/homebrew/var/www/news-app/logs/out.log
Error log:
tail -f /opt/homebrew/var/www/news-app/logs/error.log
TROUBLESHOOTING
---------------
If the service fails to start, check:
1. Node version - the plist uses:
/Users/jared/.nvm/versions/node/v22.19.0/bin/node
2. If you update Node via nvm, rebuild native modules:
cd /opt/homebrew/var/www/news-app
npm rebuild better-sqlite3
3. Then update the node path in the plist if needed and reload:
sudo launchctl bootout system/com.je.carnews
sudo launchctl bootstrap system /Library/LaunchDaemons/com.je.carnews.plist
sudo launchctl kickstart system/com.je.carnews

88
news-app/How_To_Nginx.txt Normal file
View File

@@ -0,0 +1,88 @@
Nginx Configuration for Car News App
=====================================
This app requires two route groups in your nginx server block:
1. /carnews/ - Full news app with regular and car mode views
2. /carmode/ - Standalone car mode display (no exit, no links to main app)
Both routes share the same backend API server (port 5555) and static files.
NGINX CONFIGURATION
-------------------
Add the following to your server block:
# --- Car News App ---
location = /carnews {
return 301 /carnews/;
}
location /carnews/api/ {
proxy_pass http://127.0.0.1:5555/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 180s;
proxy_connect_timeout 180s;
proxy_send_timeout 180s;
}
location /carnews/ {
alias /var/www/news-feed-car-mode/news-app/dist/;
index index.html;
try_files $uri $uri/ /carnews/index.html;
}
# --- Car Mode Direct Access ---
location = /carmode {
return 301 /carmode/;
}
location /carmode/api/ {
proxy_pass http://127.0.0.1:5555/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 180s;
proxy_connect_timeout 180s;
proxy_send_timeout 180s;
}
location /carmode/ {
alias /var/www/news-feed-car-mode/news-app/dist/;
index index.html;
try_files $uri $uri/ /carmode/index.html;
}
CONFIGURATION NOTES
-------------------
- The API timeout is set to 180s because AI grouping requests can take time
- Both /carnews/ and /carmode/ serve the same dist/ folder
- The frontend JS detects which URL it's on and adjusts behavior accordingly
- proxy_http_version 1.1 is required for proper keep-alive connections
AFTER CHANGES
-------------
Test and reload nginx:
sudo nginx -t
sudo systemctl reload nginx
BUILDING THE APP
----------------
After code changes, rebuild the frontend:
cd /var/www/news-feed-car-mode/news-app
npm run build

View File

@@ -1,61 +0,0 @@
Based on the codebase analysis, here's what needs to change for your Ubuntu deployment:
Required Changes
Create a systemd service file instead:
# /etc/systemd/system/carnews.service
[Unit]
Description=Car News API Server
After=network.target
[Service]
Type=simple
User=<your-ubuntu-user>
WorkingDirectory=/path/to/news-app
ExecStart=/usr/bin/node server.js
Restart=on-failure
EnvironmentFile=/path/to/news-app/.env
[Install]
WantedBy=multi-user.target
2. Rebuild native modules
The better-sqlite3 package requires recompilation on Ubuntu:
npm rebuild better-sqlite3
3. Nginx configuration for Ubuntu
You'll need to create an Nginx config (not included in this repo). Example:
location /carnews/ {
alias /path/to/news-app/dist/;
try_files $uri $uri/ /carnews/index.html;
}
location /carnews/api/ {
proxy_pass http://localhost:5555/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
4. Update paths if directory structure differs
On Ubuntu: /var/www/news-feed-car-mode/news-app
No Changes Needed
- vite.config.js - base: '/carnews/' stays the same
- server.js - Port 5555 works as-is
- .env file - copied over
- All source code - Uses relative paths
Deployment Steps
1. Copy the codebase to Ubuntu
2. Run npm install and npm rebuild better-sqlite3
3. Run npm run build to generate fresh /dist files
4. Create the systemd service file
5. Configure Nginx with the proxy rules
6. Enable and start the service: sudo systemctl enable --now carnews

View File

@@ -1,6 +1,10 @@
import './style.css'
const API_BASE = '/carnews/api'
// Check if we're on the dedicated car mode URL
const isCarModeUrl = window.location.pathname === '/carmode' || window.location.pathname === '/carmode/'
// Use appropriate API base depending on URL
const API_BASE = isCarModeUrl ? '/carmode/api' : '/carnews/api'
const app = document.querySelector('#app')
// Fetch with timeout helper (default 2 minutes for AI calls)
@@ -49,15 +53,16 @@ const state = {
error: null,
filter: 'all',
viewMode: 'regular', // 'regular' or 'ai'
carMode: false,
carMode: isCarModeUrl, // Auto-enter car mode if on /carmode URL
carModeIndex: 0,
carModeInterval: null,
carModeScrollTimeout: null,
carModeScrollInterval: null,
aiArticleCount: 0,
locked: !getCookie('news_feed_unlocked'),
locked: isCarModeUrl ? false : !getCookie('news_feed_unlocked'), // Skip lock for car mode URL
unlockClicks: 0,
isTransitioning: false,
isCarModeUrl: isCarModeUrl, // Track if we're on dedicated car mode URL
}
function formatDate(dateString) {
@@ -274,11 +279,13 @@ function renderCarMode() {
<div class="w-16 h-16 border-4 border-white/30 border-t-white rounded-full animate-spin mx-auto mb-6"></div>
<p class="text-xl">${loadingMessage}</p>
</div>
${!state.isCarModeUrl ? `
<button onclick="exitCarMode()" class="absolute top-14 right-6 z-20 p-3 text-white/70 hover:text-white hover:bg-white/10 rounded-full transition-colors" title="Exit Car Mode (Esc)">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
` : ''}
</div>
`
}
@@ -291,12 +298,14 @@ function renderCarMode() {
<div class="car-mode-blur-2"></div>
<div class="car-mode-blur-3"></div>
${!state.isCarModeUrl ? `
<!-- Exit button -->
<button onclick="exitCarMode()" class="absolute top-2 right-6 z-20 p-3 text-white/70 hover:text-white hover:bg-white/10 rounded-full transition-colors backdrop-blur-sm bg-black/10" title="Exit Car Mode (Esc)">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
` : ''}
<!-- Car mode indicator -->
<div class="absolute top-2 left-6 z-20 flex items-center gap-2 text-white/70 backdrop-blur-sm px-3 py-1.5 rounded-full bg-black/10">
@@ -599,6 +608,11 @@ window.enterCarMode = async function() {
}
window.exitCarMode = function() {
// On dedicated car mode URL, just refresh the page to restart
if (state.isCarModeUrl) {
window.location.reload()
return
}
state.carMode = false
stopCarModeCycle()
render()
@@ -692,7 +706,10 @@ document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'Escape':
window.exitCarMode()
// Don't allow escape on dedicated car mode URL
if (!state.isCarModeUrl) {
window.exitCarMode()
}
break
case 'ArrowRight':
case ' ':
@@ -704,9 +721,17 @@ document.addEventListener('keydown', (e) => {
}
})
fetchNews()
// Initialize: either start car mode or fetch regular news
if (isCarModeUrl) {
// On dedicated car mode URL, directly enter car mode
window.enterCarMode()
} else {
fetchNews()
}
setInterval(() => {
// Don't auto-refresh if in car mode (it handles its own data)
if (state.carMode) return
if (state.viewMode === 'ai') {
fetchGroupedNews()
} else {