This setup allows you to test changes to kyte-api-js locally without deploying to CDN.
The symlink has already been created:
/assets/js/kyte-dev.js → ../../../kyte-api-js/kyte-source.jsThis symlink is excluded from git via .gitignore.
Windows blocks unprivileged symlink creation by default, so plain ln -sf from Git Bash and New-Item -ItemType SymbolicLink from PowerShell will fail with Administrator privilege required.
Pick one:
-
Enable Developer Mode (recommended, one-time fix). Settings → Privacy & security → For developers → Developer Mode = On. After this, the standard symlink commands work without admin.
-
Run an elevated PowerShell once and create the symlink:
New-Item -ItemType SymbolicLink ` -Path "D:\path\to\kyte-shipyard\assets\js\kyte-dev.js" ` -Target "D:\path\to\kyte-api-js\kyte-source.js"
-
Hard link fallback (no admin needed, same volume only). Functionally equivalent for editing — both names point at the same file data, so changes propagate. Caveat: if
kyte-source.jsis deleted and recreated (e.g., byrelease.sh's build step or agit reset --hard), the hard link keeps pointing at the old data and must be re-created. From an unprivileged PowerShell:New-Item -ItemType HardLink ` -Path "D:\path\to\kyte-shipyard\assets\js\kyte-dev.js" ` -Target "D:\path\to\kyte-api-js\kyte-source.js"
When running on localhost or 127.0.0.1, pages automatically load:
- Local kyte.js:
/assets/js/kyte-dev.js(symlinked tokyte-api-js/kyte-source.js) - Source files:
/assets/js/source/*.js(non-obfuscated shipyard files)
When running in production, pages load:
- CDN kyte.js:
https://cdn.keyqcloud.com/kyte/js/stable/kyte.js - Obfuscated files:
/assets/js/*.js(production shipyard files)
-
Make changes to kyte-api-js:
cd "/path/to/kyte-api-js" # Edit kyte-source.js
-
Test immediately in browser:
- Navigate to
http://localhost:8080/app/emails.html - Changes are reflected automatically (just refresh)
- Console shows:
[DEV MODE] Loading local kyte-api-js from /assets/js/kyte-dev.js
- Navigate to
-
When ready to release:
cd "/path/to/kyte-api-js" ./release.sh # Builds minified versions
-
Deploy to CDN (when production-ready):
- Upload built files to CDN
- Production sites automatically use CDN version
In development mode, you'll see colored console messages:
- 🔴 Red: Loading local kyte-api-js (your changes)
- 🟢 Green: Loading local shipyard source files
Pages that support local dev mode include this script block:
<script>
// Auto-detect localhost and conditionally load local kyte.js for development
(function() {
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
// Load kyte.js: use local dev version on localhost, otherwise use CDN
const kyteScript = isLocalhost ? '/assets/js/kyte-dev.js' : 'https://cdn.keyqcloud.com/kyte/js/stable/kyte.js';
document.write('<script src="' + kyteScript + '"' + (isLocalhost ? '' : ' crossorigin="anonymous"') + '><\/script>');
if (isLocalhost) {
console.log('%c[DEV MODE] Loading local kyte-api-js from /assets/js/kyte-dev.js', 'color: #FF6B6B; font-weight: bold;');
}
})();
</script>To apply this pattern to other HTML pages, replace:
<!-- OLD -->
<script src="https://cdn.keyqcloud.com/kyte/js/stable/kyte.js" crossorigin="anonymous"></script>With the conditional loading script above (before the shipyard scripts).
Symlink broken?
cd "/path/to/kyte-managed-front-end/assets/js"
rm kyte-dev.js
ln -sf "../../../kyte-api-js/kyte-source.js" kyte-dev.jsChanges not reflecting?
- Hard refresh:
Cmd+Shift+R(Mac) orCtrl+Shift+R(Windows) - Check console for
[DEV MODE]messages - Verify you're accessing via
localhost(not127.0.0.1if expecting different behavior)
404 on kyte-dev.js?
- Ensure the symlink exists:
ls -la /assets/js/kyte-dev.js - Check that
kyte-api-jsis at the correct relative path
✅ Do:
- Test all changes locally before releasing
- Update
CHANGELOG.mdin kyte-api-js - Bump version number before release
- Test on localhost first, then staging, then production
❌ Don't:
- Commit
kyte-dev.js(it's gitignored) - Skip the
release.shbuild step - Deploy unminified source to production
- Change the symlink target (always point to
kyte-source.js)
Non-obvious things about the widgets in kyte-api-js/kyte-source.js. Save the next dev a debugging trip.
kyte-source.js:767:
new KyteTable(api, selector, model, columnDefs,
searching = true, order = [],
actionEdit = false, actionDelete = false, actionView = false,
viewTarget = null, rowCallBack = null, initComplete = null)Most call sites pass them as bare booleans, so it's easy to misread which flag is which. The two that bite people:
actionEdit/actionDelete/actionViewauto-inject a⋮kebab dropdown column with Edit / Delete / View entries. If you're rendering your own action buttons via a column renderer, set these tofalseor you'll get a duplicate column. (This is whatapp/tokens.htmldoes — its custom Revoke column needsactionDelete: false.)searchingtoggles the search input above the table.
KyteTable doesn't use DataTables' ajax config, so $('#table').DataTable().ajax.reload() is a no-op. Use kyteTableInstance.draw() (kyte-source.js:1890) — it calls _loadData() internally and refetches.
assets/css/kyte-form.css:82 sets .modal-body { padding: 0; padding-top: 1em; } with !important. KyteForm relies on its content being wrapped in a .mx-3 (which then gets padding: 2.5rem !important). Custom modals that aren't KyteForm-driven need either a .mx-3 wrapper or their own padding: ... !important override (see app/tokens.html for the .modal-mcp pattern).
kyte-form.css:25-30 paints every .modal-header with var(--primary-gradient) (orange) !important, and kyte-form.css:55-61 injects a database (\f1c0) icon before every .modal-title. If you want a different look (e.g. for AI/MCP modals), override both with !important on a scoping class. Example in app/tokens.html:
.modal-mcp .modal-header { background: linear-gradient(135deg, #667eea, #764ba2) !important; }
.modal-mcp .modal-title::before { content: '\f544' !important; /* fa-robot */ }