From bf18251f3a60c1d05c22ab66309527e5a9d72f07 Mon Sep 17 00:00:00 2001 From: Kenneth Hough Date: Sun, 31 May 2026 04:45:15 -0500 Subject: [PATCH 1/3] =?UTF-8?q?Add=20Publish=20action=20to=20the=20IDE=20(?= =?UTF-8?q?2.2.0)=20=E2=80=94=20KYTE-#189?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IDE could only Save; add a Publish action for publishable file types (pages + scripts → S3): - Green rocket button in the tab bar, shown only for page/script tabs. - Ctrl+Shift+S shortcut (Save stays Ctrl+S); added to the welcome shortcuts. - Publish sends state=1 with the content, so the backend runs its normal publish path (publishPage / handleScriptPublication → S3) and versions it. Allowed even when not dirty (re-deploy); persists pending edits in the same request. Bump KS_VERSION + package.json to 2.2.0. esbuild build passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 12 +++++ app/ide/index.html | 4 ++ assets/js/source/kyte-shipyard-ide.js | 76 ++++++++++++++++++++++++--- assets/js/source/kyte-shipyard.js | 2 +- package.json | 2 +- 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df4ff8..31611e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 2.2.0 + +### Feature: Publish action in the IDE (KYTE-#189) + +The in-app IDE could only Save (which persists content); there was no way to publish from it. Added a **Publish** action for publishable file types (**pages** and **scripts** — the ones that deploy to S3): + +- A green **rocket** button in the IDE tab bar, shown only when the active file is a page or script. +- **Ctrl+Shift+S** keyboard shortcut (Save remains Ctrl+S); added to the welcome-screen shortcut list. +- Publishing sends `state=1` alongside the content, so the backend runs its normal publish path (page → `publishPage` → S3 + CloudFront; script → `handleScriptPublication` → S3) and creates a version with the change summary. Unlike Save, Publish is allowed even when the file isn't dirty (re-deploy current content), and it persists any pending edits in the same request. + +Pairs with kyte-php v4.8.1 (`block_layout` partial-save guard). The "IDE save didn't persist" issue from #189 was already fixed by kyte-php v4.8.0. + ## 2.1.0 ### Change: remove JavaScript obfuscation (pairs with kyte-php v4.7.0 — KYTE-#191) diff --git a/app/ide/index.html b/app/ide/index.html index 89df356..32f35e3 100644 --- a/app/ide/index.html +++ b/app/ide/index.html @@ -140,6 +140,10 @@

Kyte IDE

Ctrl+S Save current file +
+ Ctrl+Shift+S + Publish current file (pages & scripts) +
Ctrl+W Close current tab diff --git a/assets/js/source/kyte-shipyard-ide.js b/assets/js/source/kyte-shipyard-ide.js index 8a74719..c80eac3 100644 --- a/assets/js/source/kyte-shipyard-ide.js +++ b/assets/js/source/kyte-shipyard-ide.js @@ -65,6 +65,7 @@ const FILE_TYPES = { iconClass: 'html', sectionIcon: 'fas fa-sitemap', model: 'KytePage', + publishable: true, // can be published to S3 (state=1 → publishPage) loadModel: 'KytePageData', // Special loader: uses KytePageData to get content loadField: 'page', // KytePageData is keyed by 'page' field listField: 'site', @@ -119,6 +120,7 @@ const FILE_TYPES = { iconClass: 'js', sectionIcon: 'fas fa-scroll', model: 'KyteScript', + publishable: true, // can be published to S3 (state=1 → handleScriptPublication) listField: 'site', parts: [ { key: 'content', label: 'JavaScript', language: 'javascript', field: 'content' } @@ -690,7 +692,16 @@ function renderTabs() { `; - tabBar.innerHTML = tabsHtml + historyBtnHtml; + // Show a Publish button only for publishable file types (pages, scripts → S3) + const isPublishable = activeTab && activeTab.fileType.publishable; + const publishBtnHtml = ` + + `; + + tabBar.innerHTML = tabsHtml + publishBtnHtml + historyBtnHtml; } function renderSubTabs(tab) { @@ -872,7 +883,7 @@ function saveFile(fileId, callback) { } } -function performSave(fileId, fileType, itemId, buf, changeSummary, callback) { +function performSave(fileId, fileType, itemId, buf, changeSummary, callback, publish) { // Build update data const data = {}; fileType.parts.forEach(part => { @@ -884,13 +895,19 @@ function performSave(fileId, fileType, itemId, buf, changeSummary, callback) { data.change_summary = changeSummary; } + // Publish: state=1 triggers the backend publish (page → S3 via publishPage, + // script → S3 via handleScriptPublication). Save without it just persists. + if (publish) { + data.state = 1; + } + // For Pages, the save target is KytePage and the ID is from the nested page object const saveModel = fileType.model; const saveId = (fileType.id === 'page' && buf.item && buf.item.page) ? buf.item.page.id : itemId; - notify('info', 'Saving...'); + notify('info', publish ? 'Publishing...' : 'Saving...'); _ks.put(saveModel, 'id', saveId, data, null, [], function(r) { // Update original to match current (no longer dirty) @@ -900,7 +917,7 @@ function performSave(fileId, fileType, itemId, buf, changeSummary, callback) { updateDirtyIndicators(fileId); renderTabs(); - notify('success', 'Saved successfully'); + notify('success', publish ? 'Published successfully' : 'Saved successfully'); // Refresh version history if panel is open if (historyPanelOpen && activeTabId === fileId) { @@ -909,7 +926,7 @@ function performSave(fileId, fileType, itemId, buf, changeSummary, callback) { if (callback) callback(); }, function(err) { - notify('error', 'Save failed: ' + err); + notify('error', (publish ? 'Publish failed: ' : 'Save failed: ') + err); }); } @@ -919,6 +936,45 @@ function saveActiveFile() { } } +// Publish the active file to its live site (pages/scripts → S3). Unlike save, +// publishing is allowed even when not dirty (re-deploy current content). It also +// persists any pending edits in the same request (state=1 + content). See KYTE-#189. +function publishActiveFile() { + if (activeTabId) { + publishFile(activeTabId); + } +} + +function publishFile(fileId, callback) { + const buf = fileBuffers[fileId]; + if (!buf) return; + + const { fileType, itemId } = parseFileId(fileId); + + if (!fileType.publishable) { + notify('info', 'This file type deploys automatically on save — no separate publish.'); + return; + } + + // Flush current editor content to buffer + if (activeTabId === fileId) { + saveEditorToBuffer(); + } + + // Versioned types capture a change summary (publish also creates a version) + if (fileType.versioning) { + showChangeSummaryModal(function(action, summary) { + if (action === 'cancel') { + return; + } + const changeSummary = action === 'save' ? summary : ''; + performSave(fileId, fileType, itemId, buf, changeSummary, callback, true); + }); + } else { + performSave(fileId, fileType, itemId, buf, null, callback, true); + } +} + // ============================================ // Change Summary Modal // ============================================ @@ -1196,8 +1252,15 @@ function initKeyboardShortcuts() { document.addEventListener('keydown', function(e) { const isCtrl = e.ctrlKey || e.metaKey; + // Ctrl+Shift+S — Publish + if (isCtrl && e.shiftKey && (e.key === 's' || e.key === 'S')) { + e.preventDefault(); + publishActiveFile(); + return; + } + // Ctrl+S — Save - if (isCtrl && e.key === 's') { + if (isCtrl && !e.shiftKey && e.key === 's') { e.preventDefault(); saveActiveFile(); } @@ -1345,6 +1408,7 @@ window.kyteIDE = { toggleSection, toggleGroup, saveActiveFile, + publishActiveFile, collapseAll, toggleHistory, restoreVersion diff --git a/assets/js/source/kyte-shipyard.js b/assets/js/source/kyte-shipyard.js index f9529cb..520b869 100644 --- a/assets/js/source/kyte-shipyard.js +++ b/assets/js/source/kyte-shipyard.js @@ -1,4 +1,4 @@ -var KS_VERSION = '2.1.0'; +var KS_VERSION = '2.2.0'; function loadScript(url, callback) { var script = document.createElement('script'); diff --git a/package.json b/package.json index 810ebe1..9d8ba05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kyte-shipyard", - "version": "2.1.0", + "version": "2.2.0", "private": true, "description": "Kyte Shipyard — admin UI for the Kyte platform. Sources in assets/js/source/, esbuild minifies to assets/js/ (gitignored).", "type": "module", From 05e65ef5b9fabfe0dc7c723d16a06bdd61e72816 Mon Sep 17 00:00:00 2001 From: Kenneth Hough Date: Sun, 31 May 2026 05:20:52 -0500 Subject: [PATCH 2/3] Add "Republish all pages" + republish result UI (KYTE-#181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the app configuration page: a "Republish all pages" button on the Auth Mode card (re-stamps every published page with the current connect code), and surface the per-page republish_summary (succeeded/failed + failures) returned by kyte-php v4.8.1 in the auth-mode/kyte-connect flows and the new button — instead of a blind success toast. Folded into the IDE-Publish branch (2.2.0). esbuild build passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 7 +++ app/configuration.html | 8 ++- ...kyte-shipyard-application-configuration.js | 60 ++++++++++++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31611e2..8c742cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## 2.2.0 +### Feature: "Republish all pages" + per-page republish result (KYTE-#181) + +Pairs with kyte-php v4.8.1, which makes the republish hook fault-isolated and returns a `republish_summary`. On the app **configuration page**: + +- A **"Republish all pages"** button (on the Authentication Mode card) — an explicit recovery action that re-stamps and re-deploys every published page with the current Kyte Connect code, then reports the result. Useful when an auto-republish half-completed. +- The **per-page result is now surfaced**: the auth-mode flip / kyte-connect update and the new button all read `republish_summary` and show how many pages succeeded/failed (failures are listed and logged to the console) — instead of a blind "it worked" toast. + ### Feature: Publish action in the IDE (KYTE-#189) The in-app IDE could only Save (which persists content); there was no way to publish from it. Added a **Publish** action for publishable file types (**pages** and **scripts** — the ones that deploy to S3): diff --git a/app/configuration.html b/app/configuration.html index e257593..ff4d15a 100644 --- a/app/configuration.html +++ b/app/configuration.html @@ -376,8 +376,12 @@

JWT Bearer (Recommended — rotating tokens)

- -
+ +
+
${items.map(item => ` -
+
- ${escapeHtml(item.name)} + + ${escapeHtml(item.name)} + ${item.path ? `${escapeHtml(item.path)}` : ''} +
`).join('')}