feat: enable app feedback by default in Dev Center

New apps created in Dev Center now have feedbackEnabled set on creation,
so users can send the developer feedback without any extra setup. A "User
Feedback" toggle in the app's settings lets developers turn it off (and
back on); it's wired into the save payload, the dirty-state tracking, and
the reset-to-original path like the neighboring toggles.

The Save update omits feedbackEnabled unless the toggle is present, and
the backend leaves an omitted field untouched, so the default survives
the create-then-save flow Dev Center runs. Add an SDK apps-suite guard
covering that round-trip (create-on -> unrelated update keeps it -> can
be turned off).
This commit is contained in:
Nariman Jelveh
2026-08-11 16:47:12 -07:00
parent 792361507e
commit 32c950de5b
2 changed files with 36 additions and 1 deletions
+14 -1
View File
@@ -191,6 +191,9 @@ async function create_app (title, source_path = null, items = null) {
maximizeOnStart: false,
background: false,
dedupeName: true,
// New apps accept user feedback by default; developers can turn this
// off in the app's settings (the "User Feedback" toggle).
feedbackEnabled: true,
metadata: {
window_resizable: true,
fullpage_on_landing: true,
@@ -509,6 +512,12 @@ function generate_edit_app_section (app) {
</div>
<h3 style="font-size: 23px; border-bottom: 1px solid #EEE; margin-top: 50px; margin-bottom: 0px;">Misc</h3>
<div style="margin-top:30px;">
<input type="checkbox" id="edit-app-user-feedback" name="edit-app-user-feedback" value="true" ${app.feedback_enabled ? 'checked' : ''}>
<label for="edit-app-user-feedback" style="display: inline;">User Feedback</label>
<p>When enabled, users can send you feedback about this app from within Puter. Feedback is delivered to your account's email address.</p>
</div>
<div style="margin-top:30px;">
<input type="checkbox" id="edit-app-locked" name="edit-app-locked" value="true" ${app.metadata?.locked ? 'checked' : ''}>
<label for="edit-app-locked" style="display: inline;">Delete Protection${lock_svg}</label>
@@ -586,6 +595,7 @@ function trackOriginalValues () {
locked: $('#edit-app-locked').is(':checked'),
fullPageOnLanding: $('#edit-app-fullpage-on-landing').is(':checked'),
setTitleToFile: $('#edit-app-set-title-to-file').is(':checked'),
userFeedback: $('#edit-app-user-feedback').is(':checked'),
},
};
}
@@ -626,7 +636,8 @@ function hasChanges () {
$('#edit-app-hide-titlebar').is(':checked') !== originalValues.checkboxes.hideTitleBar ||
$('#edit-app-locked').is(':checked') !== originalValues.checkboxes.locked ||
$('#edit-app-fullpage-on-landing').is(':checked') !== originalValues.checkboxes.fullPageOnLanding ||
$('#edit-app-set-title-to-file').is(':checked') !== originalValues.checkboxes.setTitleToFile
$('#edit-app-set-title-to-file').is(':checked') !== originalValues.checkboxes.setTitleToFile ||
$('#edit-app-user-feedback').is(':checked') !== originalValues.checkboxes.userFeedback
);
}
@@ -674,6 +685,7 @@ function resetToOriginalValues () {
$('#edit-app-locked').prop('checked', originalValues.checkboxes.locked);
$('#edit-app-fullpage-on-landing').prop('checked', originalValues.checkboxes.fullPageOnLanding);
$('#edit-app-set-title-to-file').prop('checked', originalValues.checkboxes.setTitleToFile);
$('#edit-app-user-feedback').prop('checked', originalValues.checkboxes.userFeedback);
if ( originalValues.icon ) {
$('#edit-app-icon').css('background-image', `url(${originalValues.icon})`);
@@ -1284,6 +1296,7 @@ async function saveEditedApp (mode) {
description: description,
maximizeOnStart: $('#edit-app-maximize-on-start').is(':checked'),
background: $('#edit-app-background').is(':checked'),
feedbackEnabled: $('#edit-app-user-feedback').is(':checked'),
metadata: {
fullpage_on_landing: $('#edit-app-fullpage-on-landing').is(':checked'),
social_image: socialImageUrl,
@@ -144,6 +144,28 @@ export default suite('apps', {
t.assert.equal(updated.description, 'New description');
},
'feedbackEnabled round-trips on create and survives an unrelated update':
async (t) => {
// Mirrors Dev Center: create with feedback on, then a Save-style
// update that omits feedbackEnabled must not clear it.
const created = await t.puter.apps.create({
name: 'apps-suite-feedback',
indexURL: 'https://example.com/feedback',
feedbackEnabled: true,
});
t.assert.equal(Boolean(created.feedback_enabled), true);
const updated = await t.puter.apps.update('apps-suite-feedback', {
title: 'Feedback App',
});
t.assert.equal(Boolean(updated.feedback_enabled), true);
const disabled = await t.puter.apps.update('apps-suite-feedback', {
feedbackEnabled: false,
});
t.assert.equal(Boolean(disabled.feedback_enabled), false);
},
'get of an unknown app rejects': async (t) => {
await t.assert.rejects(
() => t.puter.apps.get('apps-suite-does-not-exist'),