Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ __pycache__
huf/public/frontend
huf/public/docs
huf/www/huf.html
huf/www/chat.html
huf/www/huf/sw.js
huf/www/huf/manifest.webmanifest
huf/www/huf/workbox-*.js
huf/www/docs.html
docs/.next
docs/out
docs/node_modules
docs/yarn.lock

huf/public/_next
huf/www/huf/docs
huf/www/huf/docs
44 changes: 44 additions & 0 deletions docs/temp/chat_only_ui_tracker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Full Huf PWA Tracker

## Context
- Branch: `feat_pwa`
- Base: `origin/develop`
- Goal: full Huf app PWA experience for `/huf`, served through Frappe.
- Bench validation will be handled outside this worktree.

## Decisions
- Keep first version simple: installable PWA, static frontend asset caching only, no cached private API data.
- Use Vite build output under `/assets/huf/frontend/`.
- Serve the app shell through a Frappe `www` route.
- Full-app PWA uses manifest `start_url: /huf` and `scope: /huf/`.
- Service worker is copied from generated assets into `huf/www/huf/sw.js` and registered as `/huf/sw.js` with `/huf/` scope.
- Manifest is copied to `huf/www/huf/manifest.webmanifest` so the worker's `/huf/manifest.webmanifest` precache entry resolves through `www`.
- `feat_chat_ui` owns `/huf/ui/chat`; this branch should not duplicate chat-only route/page code.

## TODO
- [x] Inspect current Huf frontend build, routing, auth/logout, and logo/assets.
- [x] Inspect Frappe route/build integration.
- [x] Add PWA plugin and service worker registration.
- [x] Add or adapt Frappe-served app shell.
- [x] Add icons/manifest support.
- [x] Remove duplicate chat-only page and route ownership from PWA branch.
- [x] Verify build and type checks where possible.

## Scratchpad
- Current repo already has `frontend/`, `frontend/vite.config.ts`, root `package.json`, and `huf/www/agent_chat.html`.
- Root `package.json` already delegates build/dev/install to `frontend`.
- `frontend/package.json` already builds with `vite build --base=/assets/huf/frontend/` and copies generated HTML to `huf/www/huf.html`.
- `frontend/vite.config.ts` already outputs to `../huf/public/frontend`.
- `huf/hooks.py` catch-all maps `/huf/<path:app_path>` to `huf`, so `/huf/ui/chat` can use the existing Frappe shell.
- Existing `ChatWindow` uses `useSidebar`, so a standalone chat route needs a `SidebarProvider` or a small adaptation.
- PWA configured via `vite-plugin-pwa` with API and stream requests forced `NetworkOnly` and static frontend assets `StaleWhileRevalidate`.
- Generated PWA icons in `frontend/public/icons/` from `huf/public/Images/huf.png`.
- `yarn install` completed after network escalation and created `frontend/yarn.lock`.
- `frontend/yarn.lock` is ignored by repo rules (`**/yarn.lock`), so dependency persistence is in `frontend/package.json`.
- `yarn build` in `frontend/` passed as a smoke check and generated `manifest.webmanifest`, `sw.js`, `workbox-*.js`, then copied the shell to `huf/www/huf.html` and PWA route files to `huf/www/huf/`.
- Added generated `huf/www/huf/sw.js`, `huf/www/huf/manifest.webmanifest`, and `huf/www/huf/workbox-*.js` to `.gitignore` because they reference generated output from ignored `huf/public/frontend`.
- `yarn typecheck` in `frontend/` passed after the final full-app PWA adjustment.
- `yarn build` in `frontend/` passed after the final full-app PWA adjustment.
- Bench validation was not run because this worktree is not a bench environment.
- PWA branch updated to full-app scope; `huf/www/chat.html` is no longer generated by the copy script.
- Copy script removes stale generated `workbox-*.js` files from the `www` worker route before copying the current build output.
6 changes: 4 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/assets/huf/frontend/icons/icon-192.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#111827" />
<title>Huf AI</title>
</head>
<body>
Expand Down
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint": "eslint .",
"preview": "vite preview",
"typecheck": "tsc --noEmit -p tsconfig.app.json",
"copy-html-entry": "cp ../huf/public/frontend/index.html ../huf/www/huf.html"
"copy-html-entry": "node scripts/copy-html-entry.mjs"
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
Expand Down Expand Up @@ -95,6 +95,7 @@
"tailwindcss": "^3.4.13",
"typescript": "^5.5.3",
"typescript-eslint": "^8.7.0",
"vite": "^5.4.8"
"vite": "^5.4.8",
"vite-plugin-pwa": "^0.21.1"
}
}
Binary file added frontend/public/icons/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/icons/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/icons/maskable-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions frontend/scripts/copy-html-entry.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { copyFile, mkdir, readdir, unlink } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';

const frontendDir = resolve(import.meta.dirname, '..');
const repoDir = resolve(frontendDir, '..');
const source = resolve(frontendDir, '../huf/public/frontend/index.html');
const shellTarget = resolve(frontendDir, '../huf/www/huf.html');
const serviceWorkerSource = resolve(frontendDir, '../huf/public/frontend/sw.js');
const serviceWorkerTarget = resolve(frontendDir, '../huf/www/huf/sw.js');
const manifestSource = resolve(frontendDir, '../huf/public/frontend/manifest.webmanifest');
const manifestTarget = resolve(frontendDir, '../huf/www/huf/manifest.webmanifest');
const frontendOutDir = resolve(frontendDir, '../huf/public/frontend');
const serviceWorkerRouteDir = resolve(frontendDir, '../huf/www/huf');

await mkdir(dirname(shellTarget), { recursive: true });
await copyFile(source, shellTarget);

await mkdir(serviceWorkerRouteDir, { recursive: true });
await copyFile(serviceWorkerSource, serviceWorkerTarget);
await copyFile(manifestSource, manifestTarget);

const generatedFiles = await readdir(frontendOutDir);
const workboxFiles = generatedFiles.filter((file) => /^workbox-.+\.js$/.test(file));
const existingRouteFiles = await readdir(serviceWorkerRouteDir);

await Promise.all(
existingRouteFiles
.filter((file) => /^workbox-.+\.js$/.test(file))
.map((file) => unlink(resolve(serviceWorkerRouteDir, file))),
);

await Promise.all(
workboxFiles.map((file) =>
copyFile(resolve(frontendOutDir, file), resolve(serviceWorkerRouteDir, file)),
),
);

const copiedPaths = [
shellTarget,
serviceWorkerTarget,
manifestTarget,
...workboxFiles.map((file) => resolve(serviceWorkerRouteDir, file)),
];
console.log(`Copied frontend entry files to ${copiedPaths.map((target) => target.replace(`${repoDir}/`, '')).join(', ')}`);
1 change: 1 addition & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
padding: 0;
height: 100%;
width: 100%;
overscroll-behavior-y: none;
}

#root {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import './pwa/registerSW';

createRoot(document.getElementById('root')!).render(
<StrictMode>
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/pwa/registerSW.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/huf/sw.js', { scope: '/huf/' }).catch((error) => {
console.error('Failed to register Huf service worker', error);
});
});
}
63 changes: 63 additions & 0 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,73 @@ import path from 'path';
import react from '@vitejs/plugin-react';
import proxyOptions from './proxyOptions';
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
base: '/assets/huf/frontend/',
plugins: [
react(),
VitePWA({
injectRegister: false,
manifest: {
name: 'Huf',
short_name: 'Huf',
description: 'Build and run smart AI agents with tools, chat, and automation.',
start_url: '/huf',
scope: '/huf/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#111827',
icons: [
{
src: '/assets/huf/frontend/icons/icon-192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/assets/huf/frontend/icons/icon-512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: '/assets/huf/frontend/icons/maskable-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
},
workbox: {
navigateFallback: null,
globPatterns: ['**/*.{js,css,ico,png,svg,woff,woff2,ttf}'],
modifyURLPrefix: {
'': '/assets/huf/frontend/',
},
runtimeCaching: [
{
urlPattern: ({ url }) => url.pathname.startsWith('/api/'),
handler: 'NetworkOnly',
options: {
cacheName: 'huf-api-network-only',
},
},
{
urlPattern: ({ url }) => url.pathname.startsWith('/huf/stream/'),
handler: 'NetworkOnly',
options: {
cacheName: 'huf-stream-network-only',
},
},
{
urlPattern: ({ url }) => url.pathname.startsWith('/assets/huf/frontend/'),
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'huf-frontend-assets',
},
},
],
},
}),
],
resolve: {
alias: {
Expand Down
3 changes: 3 additions & 0 deletions huf/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
# Home Pages
# ----------
website_route_rules = [
{"from_route": "/huf/sw.js", "to_route": "huf/sw.js"},
{"from_route": "/huf/manifest.webmanifest", "to_route": "huf/manifest.webmanifest"},
{"from_route": "/huf/workbox-<path:filename>", "to_route": "huf/workbox-<path:filename>"},
{"from_route": "/huf/stream/ping", "to_route": "huf/stream/ping"},
{"from_route": "/huf/stream/<path:agent_name>", "to_route": "huf/stream"},
{"from_route": "/huf/stream", "to_route": "huf/stream"},
Expand Down