fix: replace innerHTML with insertAdjacentHTML/replaceChildren; revert docker-compose dev changes

- birthdays.js: all innerHTML writes replaced with replaceChildren() + insertAdjacentHTML()
- dashboard.js: shell.innerHTML replaced with replaceChildren() + insertAdjacentHTML()
- docker-compose.yml: revert port to 3000 and restore image line (were personal dev changes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ulas Kalayci
2026-04-27 07:32:36 +02:00
parent 08199495b6
commit c9ba68cc9b
3 changed files with 27 additions and 19 deletions
+3 -3
View File
@@ -1,11 +1,11 @@
services: services:
oikos: oikos:
# image: ghcr.io/ulsklyc/oikos:latest image: ghcr.io/ulsklyc/oikos:latest
build: . # optional: use --build to build locally instead build: . # optional: use --build to build locally instead
container_name: oikos container_name: oikos
restart: unless-stopped restart: unless-stopped
ports: ports:
- "0.0.0.0:3100:3000" - "0.0.0.0:3000:3000"
volumes: volumes:
- oikos_data:/data - oikos_data:/data
env_file: env_file:
@@ -19,7 +19,7 @@ services:
# Direct HTTP access (no reverse proxy): # Direct HTTP access (no reverse proxy):
- SESSION_SECURE=false - SESSION_SECURE=false
healthcheck: healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3100/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"] test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
+21 -14
View File
@@ -65,11 +65,12 @@ function renderSuggestions() {
const items = suggestions(); const items = suggestions();
if (!items.length) { if (!items.length) {
dropdown.hidden = true; dropdown.hidden = true;
dropdown.innerHTML = ''; dropdown.replaceChildren();
return; return;
} }
dropdown.hidden = false; dropdown.hidden = false;
dropdown.innerHTML = items.map((birthday, idx) => ` dropdown.replaceChildren();
dropdown.insertAdjacentHTML('beforeend', items.map((birthday, idx) => `
<button class="birthday-suggestion" type="button" data-index="${idx}" data-name="${esc(birthday.name)}"> <button class="birthday-suggestion" type="button" data-index="${idx}" data-name="${esc(birthday.name)}">
${photoAvatar(birthday, 'birthday-avatar--xs')} ${photoAvatar(birthday, 'birthday-avatar--xs')}
<span> <span>
@@ -77,20 +78,22 @@ function renderSuggestions() {
<small>${esc(ageNote(birthday))}</small> <small>${esc(ageNote(birthday))}</small>
</span> </span>
</button> </button>
`).join(''); `).join(''));
} }
function renderUpcoming() { function renderUpcoming() {
const host = _container.querySelector('#birthdays-upcoming'); const host = _container.querySelector('#birthdays-upcoming');
if (!host) return; if (!host) return;
if (!state.upcoming.length) { if (!state.upcoming.length) {
host.innerHTML = `<div class="empty-state empty-state--compact"> host.replaceChildren();
host.insertAdjacentHTML('beforeend', `<div class="empty-state empty-state--compact">
<div class="empty-state__title">${t('birthdays.emptyTitle')}</div> <div class="empty-state__title">${t('birthdays.emptyTitle')}</div>
<div class="empty-state__description">${t('birthdays.emptyDescription')}</div> <div class="empty-state__description">${t('birthdays.emptyDescription')}</div>
</div>`; </div>`);
return; return;
} }
host.innerHTML = state.upcoming.map((birthday) => ` host.replaceChildren();
host.insertAdjacentHTML('beforeend', state.upcoming.map((birthday) => `
<article class="birthday-card"> <article class="birthday-card">
<div class="birthday-card__media">${photoAvatar(birthday)}</div> <div class="birthday-card__media">${photoAvatar(birthday)}</div>
<div class="birthday-card__body"> <div class="birthday-card__body">
@@ -106,7 +109,7 @@ function renderUpcoming() {
<div class="birthday-card__note">${esc(ageNote(birthday))}</div> <div class="birthday-card__note">${esc(ageNote(birthday))}</div>
</div> </div>
</article> </article>
`).join(''); `).join(''));
} }
function renderList() { function renderList() {
@@ -114,14 +117,16 @@ function renderList() {
if (!host) return; if (!host) return;
const list = filteredBirthdays(); const list = filteredBirthdays();
if (!list.length) { if (!list.length) {
host.innerHTML = `<div class="empty-state"> host.replaceChildren();
host.insertAdjacentHTML('beforeend', `<div class="empty-state">
<div class="empty-state__title">${t('birthdays.emptyTitle')}</div> <div class="empty-state__title">${t('birthdays.emptyTitle')}</div>
<div class="empty-state__description">${t('birthdays.emptyDescription')}</div> <div class="empty-state__description">${t('birthdays.emptyDescription')}</div>
</div>`; </div>`);
return; return;
} }
host.innerHTML = list.map((birthday) => ` host.replaceChildren();
host.insertAdjacentHTML('beforeend', list.map((birthday) => `
<article class="birthday-item" data-id="${birthday.id}"> <article class="birthday-item" data-id="${birthday.id}">
<div class="birthday-item__media">${photoAvatar(birthday)}</div> <div class="birthday-item__media">${photoAvatar(birthday)}</div>
<div class="birthday-item__body"> <div class="birthday-item__body">
@@ -142,14 +147,15 @@ function renderList() {
</button> </button>
</div> </div>
</article> </article>
`).join(''); `).join(''));
if (window.lucide) window.lucide.createIcons(); if (window.lucide) window.lucide.createIcons();
stagger(host.querySelectorAll('.birthday-item')); stagger(host.querySelectorAll('.birthday-item'));
} }
function renderPage() { function renderPage() {
_container.innerHTML = ` _container.replaceChildren();
_container.insertAdjacentHTML('beforeend', `
<div class="birthdays-page"> <div class="birthdays-page">
<h1 class="sr-only">${t('birthdays.title')}</h1> <h1 class="sr-only">${t('birthdays.title')}</h1>
<div class="birthdays-toolbar"> <div class="birthdays-toolbar">
@@ -194,7 +200,7 @@ function renderPage() {
<i data-lucide="plus" style="width:24px;height:24px" aria-hidden="true"></i> <i data-lucide="plus" style="width:24px;height:24px" aria-hidden="true"></i>
</button> </button>
</div> </div>
`; `);
renderUpcoming(); renderUpcoming();
renderList(); renderList();
@@ -304,7 +310,8 @@ function openBirthdayModal({ mode, birthday = null }) {
const nameInput = panel.querySelector('#bd-name'); const nameInput = panel.querySelector('#bd-name');
const preview = panel.querySelector('#birthday-preview'); const preview = panel.querySelector('#birthday-preview');
const renderPreview = () => { const renderPreview = () => {
preview.innerHTML = birthdayPreviewHtml(nameInput.value.trim(), photoData); preview.replaceChildren();
preview.insertAdjacentHTML('beforeend', birthdayPreviewHtml(nameInput.value.trim(), photoData));
}; };
nameInput.addEventListener('input', renderPreview); nameInput.addEventListener('input', renderPreview);
panel.querySelector('#bd-photo').addEventListener('change', async (e) => { panel.querySelector('#bd-photo').addEventListener('change', async (e) => {
+3 -2
View File
@@ -1061,10 +1061,11 @@ export async function render(container, { user }) {
function rebuildDashboard(cfg) { function rebuildDashboard(cfg) {
const shell = container.querySelector('#dashboard-shell'); const shell = container.querySelector('#dashboard-shell');
if (!shell) return; if (!shell) return;
shell.innerHTML = ` shell.replaceChildren();
shell.insertAdjacentHTML('beforeend', `
${renderDashboardOverview(user, stats, weather)} ${renderDashboardOverview(user, stats, weather)}
${renderDashboardLayout(cfg, data, weather, currency)} ${renderDashboardLayout(cfg, data, weather, currency)}
`; `);
wireLinks(container, rerender); wireLinks(container, rerender);
if (window.lucide) window.lucide.createIcons(); if (window.lucide) window.lucide.createIcons();
wireWeatherRefresh(container, (updatedWeather) => { wireWeatherRefresh(container, (updatedWeather) => {