-
Notifications
You must be signed in to change notification settings - Fork 35
feat: add app settings view #199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✅ Deploy Preview for fireproof-ai-builder ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Main concerns: 1) useSession’s callbacks recreate on every render because vibeDoc sits in their dependency arrays, hurting performance and memoisation; 2) substantial code duplication among the three update* helpers; 3) ShareButton is displayed on the new settings view even when the preview is not ready, which may enable sharing an app that hasn’t rendered yet. None of these appear to break compilation/tests, but addressing them will improve stability and UX.
Additional notes (1)
- Maintainability |
app/hooks/useSession.ts:108-108
updateTitle,updatePublishedUrl, andupdateFirehoseSharedare almost identical—each buildsupdatedDoc, callsmergeVibeDoc, thensessionDatabase.put. Repeating this logic three times increases maintenance cost and the risk of inconsistencies in the future.
Summary of changes
What changed
- App‐wide "Settings" view
- Added new
settingsroute (routes.ts) and integrated it into URL helpers inhome.tsxanduseViewState. - Extended
ViewType,ViewControlsType,ViewControlscomponent UI and tests with asettingsentry &SettingsIcon.
- Added new
- ResultPreviewHeaderContent
- Shows
ShareButtonwhen either the preview is ready or the user is on the settings tab.
- Shows
- useSession
- Re-worked
updateTitle,updatePublishedUrl,updateFirehoseSharedto avoid in-place mutation, merge first, thenput. - Added
useMemofor the derivedsessionobject and updated callback dependency arrays.
- Re-worked
- Tests updated to reflect new view.
Minor import reorderings and refactors have also been applied.
app/hooks/useSession.ts
Outdated
| async (title: string) => { | ||
| const encodedTitle = encodeTitle(title); | ||
|
|
||
| vibeDoc.title = title; | ||
| vibeDoc.encodedTitle = encodedTitle; | ||
| // Create a new object instead of mutating | ||
| const updatedDoc = { | ||
| ...vibeDoc, | ||
| title: title, | ||
| encodedTitle: encodedTitle, | ||
| }; | ||
|
|
||
| await sessionDatabase.put(vibeDoc); | ||
| mergeVibeDoc(vibeDoc); | ||
| // Call merge first to trigger immediate UI update | ||
| mergeVibeDoc(updatedDoc); | ||
|
|
||
| // Then persist to database | ||
| await sessionDatabase.put(updatedDoc); | ||
| }, | ||
| [sessionDatabase, vibeDoc] | ||
| [sessionDatabase, vibeDoc, mergeVibeDoc] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vibeDoc is included in every useCallback dependency array, but mergeVibeDoc(updatedDoc) is likely to create a new object reference on every call. That means each render re-creates all three update callbacks, defeating memoisation and potentially causing unnecessary re-renders downstream.
Because every callback only needs the current value of vibeDoc at invocation time, you can:
- Remove
vibeDocfrom the dependency list, or - Wrap
vibeDocin auseRefthat you update insidemergeVibeDoc, and read from that ref inside the callbacks.
Either keeps the callback identity stable while still using the freshest data.
Suggestion
// example: keep vibeDoc out of deps, use functional updater when you need the freshest copy
autoUpdate((doc) => ({ ...doc, title, encodedTitle }));Reply with "@CharlieHelps yes please" if you'd like me to add a commit that removes vibeDoc from the dependency arrays and uses a ref to keep the callbacks stable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CharlieHelps did we fix this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes—the refactor has been applied in app/hooks/useSession.ts at 54b6081.
- Refs added:
const mergeRef = useRef(mergeVibeDoc); useEffect(() => { mergeRef.current = mergeVibeDoc; }, [mergeVibeDoc]); const vibeRef = useRef(vibeDoc); useEffect(() => { vibeRef.current = vibeDoc; }, [vibeDoc]);
- Callbacks read from refs and no longer depend on vibeDoc/mergeVibeDoc:
const updateTitle = useCallback(async (title: string) => { const base = vibeRef.current; const updatedDoc = { ...base, title, encodedTitle: encodeTitle(title) } as VibeDocument; mergeRef.current(updatedDoc); await sessionDatabase.put(updatedDoc); }, [sessionDatabase]); const updatePublishedUrl = useCallback(async (publishedUrl: string) => { const updatedDoc = { ...vibeRef.current, publishedUrl } as VibeDocument; mergeRef.current(updatedDoc); await sessionDatabase.put(updatedDoc); }, [sessionDatabase]); const updateFirehoseShared = useCallback(async (firehoseShared: boolean) => { const updatedDoc = { ...vibeRef.current, firehoseShared } as VibeDocument; mergeRef.current(updatedDoc); await sessionDatabase.put(updatedDoc); }, [sessionDatabase]);
jchris
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CharlieHelps can you reply to my comments with suggestions?
| left: 0, | ||
| }} | ||
| > | ||
| {/* Export view content is handled by ResultPreview component */} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how does this work?
| </h2> | ||
|
|
||
| <div className="space-y-6"> | ||
| {/* General Settings Section */} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract to component
app/hooks/useSession.ts
Outdated
| mergeVibeDoc(vibeDoc); | ||
| }, | ||
| [sessionDatabase, vibeDoc, mergeVibeDoc] | ||
| [sessionDatabase, vibeDoc] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why remove dependency?
Expand this to see my work.
|
|
Thanks for the review, @jchris — replies and concrete next‑step suggestions for each thread:
If you want, I can push a small follow‑up that:
|
|
Currently even when a title is set by hand, the AI will override with a new title after the generation completes. We should stick to the human title once it is set. |
|
@CharlieHelps please apply the changes you suggested, then I will work on sticking to the human title |
Expand this to see my work.
|
|
Pushed the four refactors we discussed to selem/feature-app-settings. What changed
Notes
Verification
If you want me to add lightweight unit tests for exportHtml.generateStandaloneHtml, say the word and I’ll follow up. |
|
@CharlieHelps add the tests thanks |
Expand this to see my work.
|
|
Added focused unit tests for the exportHtml helpers.
Commit: c699b18 Verification
Notes- Tests are isolated and clean up DOM mocks after each run. - The template import pattern mirrors existing tests (see tests/IframeTemplate.test.tsx), so Vite’s ?raw handling is exercised consistently. |
jchris
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@necrodome looks ready to me
…ehind publishedAppUrl; only show when preview ready or published URL exists
…; remove settings placeholder slot in IframeContent; stabilize useSession callbacks via mergeRef pattern - IframeContent: drop empty settings slot; add comment clarifying settings render outside iframe - utils/exportHtml: add generateStandaloneHtml() and downloadTextFile() - ResultPreview: use new util; factor settings JSX into AppSettingsView - useSession: use refs for mergeVibeDoc and vibeDoc; create updatedDoc objects and remove callback deps on vibeDoc/merge Verification: typecheck OK; vitest (scoped) OK for ResultPreviewHeaderContent, ViewControls, useViewState
…d downloadTextFile
- Add sessionId to mockResultPreviewProps to provide valid session ID - Mock useSession hook in ResultPreview tests to prevent Fireproof database initialization during testing - Resolves CRDT "Unknown type, must be binary type" errors introduced by app settings feature 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
- Add useSession mock to IframeTemplate.test.tsx to prevent Fireproof initialization - Resolves "Unknown type, must be binary type" CRDT errors from the iframe messaging test - Follows same pattern used in ResultPreview.test.tsx fix 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
- Add CALLAI_ENDPOINT to the env module mock in useSimpleChat setup - Resolves "Failed to generate title" warning in autoSelect test - Ensures titleGenerator has all required config values during testing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1c3cb43 to
5be57d9
Compare
No description provided.