From 37b7602027d413ddfd345df884fb584d8f6e8694 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 8 Feb 2026 05:13:31 -0500 Subject: [PATCH] chore: update DPO contact information across legal pages - Replace specific DPO name with generic privacy email across all legal templates - Update DPA to clarify third-party services vs sub-processors distinction - Add privacy policy and DPA cross-references in Terms - Add intellectual property section to Terms - Improve prompts UI with Yes/No buttons, section headers, and better visual hierarchy Co-Authored-By: Claude Sonnet 4.5 --- portal/templates/dpa.tmpl | 6 +- portal/templates/privacy.tmpl | 2 +- portal/templates/prompts.tmpl | 137 ++++++++++++++++++++++++++++------ portal/templates/terms.tmpl | 15 +++- 4 files changed, 131 insertions(+), 29 deletions(-) diff --git a/portal/templates/dpa.tmpl b/portal/templates/dpa.tmpl index 1434fb5..911e8fb 100644 --- a/portal/templates/dpa.tmpl +++ b/portal/templates/dpa.tmpl @@ -119,8 +119,8 @@

Data Processor.

inou. We store, encrypt, and transmit your data according to your instructions.

-

Sub-processors.

-

Third-party services you explicitly connect to your account, such as AI assistants. We do not use sub-processors for storage or core functionality.

+

Third-party services.

+

You may connect external services to your account, such as AI assistants. These services operate as independent controllers or as processors engaged directly by you — not as our sub-processors. We do not engage sub-processors for storage or core functionality.

@@ -238,7 +238,7 @@

Contact

-

Data Protection Officer: Johan Jongsma

+

Data Protection Officer: privacy@inou.com

Questions about data processing: privacy@inou.com

This agreement was last updated on February 8, 2026.

diff --git a/portal/templates/privacy.tmpl b/portal/templates/privacy.tmpl index 92571b0..39cfc54 100644 --- a/portal/templates/privacy.tmpl +++ b/portal/templates/privacy.tmpl @@ -234,7 +234,7 @@

We comply with FADP (Swiss data protection), GDPR (European data protection), and HIPAA (US medical privacy) standards. Regardless of where you live, you get our highest level of protection.

We may update this policy. Registered users will be notified by email of material changes. Continued use after changes constitutes acceptance.

Regardless of your jurisdiction, you may request access to your data, correction of inaccuracies, or complete deletion of your account. We will respond within 30 days.

-

Our Data Protection Officer is Johan Jongsma. For all privacy and data protection inquiries, contact privacy@inou.com.

+

Data Protection Officer: privacy@inou.com

This policy was last updated on February 8, 2026.

diff --git a/portal/templates/prompts.tmpl b/portal/templates/prompts.tmpl index 00b3fe1..53a25a1 100644 --- a/portal/templates/prompts.tmpl +++ b/portal/templates/prompts.tmpl @@ -24,6 +24,9 @@
{{/* 1. FREEFORM CARD - Always visible */}} +
+ Add New Entry +
{{range .DuePrompts}} {{if .IsFreeform}}
@@ -49,6 +52,9 @@ {{end}} {{/* 2. PENDING CARDS - Due but not filled yet */}} +
+ Due Now +
{{range .DuePrompts}} {{if not .IsFreeform}} {{if not .HasResponse}} @@ -81,11 +87,11 @@
{{else if eq .Type "checkbox"}} - +
+ + + +
{{end}} {{end}} {{end}} @@ -98,6 +104,11 @@ {{end}} {{/* 3. FILLED CARDS - Entries from today */}} + {{if .Entries}} +
+ Completed Today +
+ {{end}} {{range .Entries}}
@@ -430,6 +441,35 @@ font-size: 0.9rem; color: var(--text-muted); } +.prompt-buttons { + display: flex; + gap: 8px; +} +.prompt-btn { + padding: 10px 24px; + border: 2px solid #ddd; + border-radius: 6px; + background: white; + font-size: 0.95rem; + font-weight: 500; + color: var(--text); + cursor: pointer; + transition: all 0.15s ease; + min-width: 80px; +} +.prompt-btn:hover { + border-color: var(--accent); + background: #fff8f6; +} +.prompt-btn.selected { + background: var(--accent); + border-color: var(--accent); + color: white; +} +.prompt-btn:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(198, 93, 7, 0.2); +} .prompt-scale { display: flex; gap: 8px; @@ -521,24 +561,43 @@ font-style: italic; } .prompt-freeform { - background: #fefce8; - border-left: 4px solid #eab308; + background: #fff; + border-left: 3px solid #94a3b8; } .prompt-pending { background: #fff; - border-left: 4px solid var(--accent); + border-left: 3px solid var(--accent); } .prompt-filled { - background: #f0fdf4; - border-left: 4px solid #16a34a; + background: #fafafa; + border-left: 3px solid #64748b; } .prompt-preview { - opacity: 0.6; + opacity: 0.5; } .prompt-preview input[disabled] { cursor: not-allowed; background: #f9fafb; } +.prompt-question { + font-size: 1rem; + font-weight: 600; + color: #1e293b; +} +.prompt-category { + display: inline-block; + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #64748b; + margin-right: 12px; +} +.prompt-due { + font-size: 0.8rem; + color: #64748b; + font-weight: 500; +} .entry-readonly .prompt-field-row { display: flex; align-items: center; @@ -652,7 +711,11 @@ document.querySelectorAll('.prompt-item').forEach(item => { item.querySelectorAll('input[type=checkbox]').forEach(cb => { cb.addEventListener('change', () => saveItem(item)); }); - + + item.querySelectorAll('.prompt-btn').forEach(btn => { + btn.addEventListener('click', () => selectYesNo(btn)); + }); + item.querySelectorAll('.prompt-scale-btn').forEach(btn => { btn.addEventListener('click', () => { const field = btn.dataset.field; @@ -723,6 +786,25 @@ function editPrompt(btn) { item.querySelector('.prompt-form input:not([type=hidden]), .prompt-form textarea')?.focus(); } +function selectYesNo(btn) { + const item = btn.closest('.prompt-item'); + const field = btn.dataset.field; + const value = btn.dataset.value; + + // Deselect siblings + btn.parentElement.querySelectorAll('.prompt-btn').forEach(b => b.classList.remove('selected')); + + // Select this one + btn.classList.add('selected'); + + // Update hidden input + const hidden = item.querySelector('input[name="field_' + field + '"]'); + if (hidden) hidden.value = value; + + // Auto-save + saveItem(item); +} + function addNewPromptCard(prompt) { const container = document.querySelector('.prompts-section'); if (!container) return; @@ -737,10 +819,11 @@ function addNewPromptCard(prompt) { prompt.input_config.fields.forEach(field => { if (field.type === 'checkbox') { fieldsHtml += ` - `; +
+ + + +
`; } else if (field.type === 'number') { const step = field.step || (field.datatype === 'float' ? '0.1' : '1'); const min = field.min !== undefined ? `min="${field.min}"` : ''; @@ -820,7 +903,7 @@ function addNewPromptCard(prompt) { async function saveItem(item) { const form = item.querySelector('.prompt-form'); const promptId = form.dataset.promptId; - const inputs = form.querySelectorAll('input:not([type=hidden]), textarea'); + const inputs = form.querySelectorAll('input, textarea'); const response = {}; let hasValue = false; @@ -828,6 +911,8 @@ async function saveItem(item) { let responseRaw = ''; inputs.forEach(input => { + if (!input.name) return; // Skip inputs without names + const key = input.name.replace('field_', '').replace('response_raw', 'raw'); if (input.type === 'checkbox') { if (input.checked) { @@ -836,7 +921,13 @@ async function saveItem(item) { displayValue = '✓'; responseRaw = 'yes'; } - } else if (input.value) { + } else if (input.type === 'hidden' && input.value && input.name.startsWith('field_')) { + // Handle hidden inputs from Yes/No buttons + response[key] = input.value; + hasValue = true; + displayValue = input.value === 'yes' ? '✓' : '✗'; + responseRaw = input.value; + } else if (input.value && input.type !== 'hidden') { response[key] = input.value; hasValue = true; displayValue += (displayValue ? ' / ' : '') + input.value; @@ -974,11 +1065,11 @@ function addPendingCard(prompt) {
`; } else if (field.type === 'checkbox') { fieldsHtml = ` - `; +
+ + + +
`; } } diff --git a/portal/templates/terms.tmpl b/portal/templates/terms.tmpl index 217191d..94de653 100644 --- a/portal/templates/terms.tmpl +++ b/portal/templates/terms.tmpl @@ -121,12 +121,17 @@

Our responsibilities

What we provide.

-

We will store your data securely using FIPS 140-3 validated encryption, make it available to you through the platform, and transmit it to third-party services you explicitly authorize. We will notify you of material changes to these terms or our privacy practices.

+

We will store your data securely using FIPS 140-3 validated encryption, make it available to you through the platform, and transmit it to third-party services you explicitly authorize. For details on how we protect your data, see our security page. We will notify you of material changes to these terms or our privacy practices.

What we don't guarantee.

We aim for continuous availability but cannot guarantee it. The service may be temporarily unavailable for maintenance, updates, or circumstances beyond our control. We are not liable for decisions you or others make based on data viewed through the platform.

+
+

Privacy and data processing

+

Your use of inou is subject to our Privacy Policy and Data Processing Agreement, which describe what data we collect, how we use it, and your rights. By using the service, you acknowledge and agree to those terms.

+
+

Acceptable use

@@ -137,6 +142,12 @@

We may suspend or terminate your account. In cases of illegal activity, we will cooperate with law enforcement.

+
+

Intellectual property

+

Your data is yours. You retain all rights to the health data, files, and information you upload. We claim no ownership over your content.

+

The inou platform — its software, design, branding, and documentation — is our property. These terms grant you a personal, non-exclusive, non-transferable license to use the service. They do not grant you rights to our code, design, or brand.

+
+

Payment

@@ -175,7 +186,7 @@

Changes

We may update these terms. Registered users will be notified by email of material changes. Continued use after changes constitutes acceptance.

-

Questions: privacy@inou.com

+

Data Protection Officer: privacy@inou.com

Last updated: February 8, 2026.