Skip to content

feat(simple-table-app): redesign app using plain HTML/CSS/TS#758

Open
nicomiguelino wants to merge 6 commits intomasterfrom
feat/redesign-simple-table-app
Open

feat(simple-table-app): redesign app using plain HTML/CSS/TS#758
nicomiguelino wants to merge 6 commits intomasterfrom
feat/redesign-simple-table-app

Conversation

@nicomiguelino
Copy link
Contributor

@nicomiguelino nicomiguelino commented Mar 26, 2026

User description

Summary

  • Migrate from Vue/SCSS stack to plain HTML, CSS, and TypeScript
  • Apply Edge Apps 2026 design tokens (glass-morphism card, dark theme, Inter font)
  • Use inset row dividers that don't touch the left/right edges of the table
  • Preserve original Vue implementation in simple-table-app-old/

PR Type

Enhancement, Tests, Documentation


Description

  • Replace Vue app with vanilla TypeScript

  • Add responsive glassmorphism table interface

  • Add screenshot coverage for supported resolutions

  • Adopt shared scripts and remove Vue tooling


Diagram Walkthrough

flowchart LR
  s["screenly settings"]
  m["src/main.ts CSV parsing and rendering"]
  h["index.html static table shell"]
  c["src/css/style.css responsive glass styling"]
  t["e2e/screenshots.spec.ts screenshot validation"]
  s -- "provides content and title" --> m
  m -- "populates" --> h
  c -- "styles" --> h
  h -- "validated by" --> t
Loading

File Walkthrough

Relevant files
Tests
4 files
screenshots.spec.ts
Add screenshot coverage across supported viewports             
+41/-0   
vue.spec.ts
Remove obsolete Vue Playwright smoke test                               
+0/-42   
App.spec.ts
Delete Vue app component unit test                                             
+0/-63   
test-setup.ts
Delete shared Screenly Vitest setup mock                                 
+0/-17   
Configuration changes
12 files
eslint.config.ts
Drop local Vue ESLint flat config                                               
+0/-34   
playwright.config.ts
Remove local Playwright configuration file                             
+0/-14   
vite.config.ts
Remove custom Vite app configuration                                         
+0/-47   
vitest.config.ts
Drop local Vitest configuration file                                         
+0/-15   
main.scss
Delete legacy SCSS entry import                                                   
+0/-3     
.ignore
Ignore local installed dependencies folder                             
+1/-0     
extensions.json
Remove Vue-focused VS Code recommendations                             
+0/-9     
tsconfig.json
Remove dedicated E2E TypeScript config                                     
+0/-4     
tsconfig.app.json
Delete Vue app TypeScript configuration                                   
+0/-16   
tsconfig.json
Remove project reference TypeScript config                             
+0/-14   
tsconfig.node.json
Delete node tooling TypeScript config                                       
+0/-19   
tsconfig.vitest.json
Remove Vitest TypeScript configuration file                           
+0/-11   
Enhancement
3 files
main.ts
Render CSV tables with vanilla DOM                                             
+63/-7   
style.css
Add responsive glass table styling                                             
+180/-0 
index.html
Replace app shell with static layout                                         
+22/-3   
Miscellaneous
6 files
settings.ts
Remove Pinia locale and timezone store                                     
+0/-63   
App.vue
Remove root Vue bootstrap component                                           
+0/-94   
TableDisplay.vue
Delete Vue table display component                                             
+0/-62   
TimeDisplay.vue
Remove live Vue time display                                                         
+0/-103 
table-display.scss
Remove SCSS table component styles                                             
+0/-197 
time-display.scss
Delete SCSS time display styles                                                   
+0/-62   
Documentation
1 files
README.md
Rewrite README for shared app workflow                                     
+32/-55 
Dependencies
1 files
package.json
Adopt shared scripts and slimmer dependencies                       
+22/-52 
Additional files
11 files
1080x1920.webp [link]   
1280x720.webp [link]   
1920x1080.webp [link]   
2160x3840.webp [link]   
2160x4096.webp [link]   
3840x2160.webp [link]   
4096x2160.webp [link]   
480x800.webp [link]   
720x1280.webp [link]   
800x480.webp [link]   
bg.webp [link]   

- Migrate from Vue/SCSS to plain HTML, CSS, and TypeScript
- Add glass-morphism table card using Edge Apps 2026 design tokens
- Use inset row dividers that don't touch the left/right edges
- Preserve original Vue implementation in simple-table-app-old/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino self-assigned this Mar 26, 2026
@github-actions
Copy link

github-actions bot commented Mar 26, 2026

PR Reviewer Guide 🔍

(Review updated until commit 63d1773)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

CSV parsing

parseCSV now splits rows on every comma, so valid CSV with quoted commas is parsed incorrectly. For example, Name,Note\nAlice,"New York, NY" will render three cells in the second row instead of two, breaking common CSV inputs.

function parseCSV(csv: string): string[][] {
  return csv
    .trim()
    .split('\n')
    .map((row) => row.split(',').map((cell) => cell.trim()))
    .filter((row) => row.length > 0 && row.some((cell) => cell !== ''))
Double escaping

escapeHtml(...) is applied before assigning to textContent, which already renders values as plain text. As a result, data containing &, <, >, or quotes will be shown with literal entities like &amp; and &lt; instead of the original characters.

headers.forEach((header) => {
  const th = document.createElement('th')
  th.textContent = escapeHtml(header)
  headerRow.appendChild(th)
})
thead.appendChild(headerRow)

dataRows.forEach((row) => {
  const tr = document.createElement('tr')
  row.forEach((cell) => {
    const td = document.createElement('td')
    td.textContent = escapeHtml(cell)

@github-actions
Copy link

github-actions bot commented Mar 26, 2026

PR Code Suggestions ✨

Latest suggestions up to 63d1773
Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Make CSV parsing quote-aware

The current parser breaks on valid CSV such as quoted fields, escaped quotes, CRLF
line endings, or commas inside values. That will corrupt table data for common
inputs, so use a quote-aware parser instead of splitting on ',' and '\n'.

edge-apps/simple-table-app/src/main.ts [11-17]

 function parseCSV(csv: string): string[][] {
-  return csv
-    .trim()
-    .split('\n')
-    .map((row) => row.split(',').map((cell) => cell.trim()))
-    .filter((row) => row.length > 0 && row.some((cell) => cell !== ''))
+  const rows: string[][] = []
+  let row: string[] = []
+  let cell = ''
+  let inQuotes = false
+
+  for (let i = 0; i < csv.length; i++) {
+    const char = csv[i]
+    const nextChar = csv[i + 1]
+
+    if (char === '"') {
+      if (inQuotes && nextChar === '"') {
+        cell += '"'
+        i++
+      } else {
+        inQuotes = !inQuotes
+      }
+      continue
+    }
+
+    if (!inQuotes && char === ',') {
+      row.push(cell.trim())
+      cell = ''
+      continue
+    }
+
+    if (!inQuotes && (char === '\n' || char === '\r')) {
+      if (char === '\r' && nextChar === '\n') {
+        i++
+      }
+
+      row.push(cell.trim())
+      if (row.some((value) => value !== '')) {
+        rows.push(row)
+      }
+
+      row = []
+      cell = ''
+      continue
+    }
+
+    cell += char
+  }
+
+  row.push(cell.trim())
+  if (row.some((value) => value !== '')) {
+    rows.push(row)
+  }
+
+  return rows
 }
Suggestion importance[1-10]: 8

__

Why: parseCSV currently splits on raw commas and \n, so valid CSV with quoted commas, escaped quotes, or CRLF line endings will be parsed into incorrect cells and rows. This addresses a meaningful correctness issue in src/main.ts that can corrupt displayed table data.

Medium
Remove double-escaping of text

textContent already escapes HTML, so pre-escaping with escapeHtml will render
entities literally in the table. Values like AT&T or will appear incorrectly, so
assign the raw string directly.

edge-apps/simple-table-app/src/main.ts [32-41]

-th.textContent = escapeHtml(header)
+th.textContent = header
 ...
-td.textContent = escapeHtml(cell)
+td.textContent = cell
Suggestion importance[1-10]: 7

__

Why: Using escapeHtml(...) before assigning to textContent will render entities literally, so values like AT&T or <Sales> can display incorrectly. The suggestion is accurate and improves output correctness while remaining safe because textContent already prevents HTML injection.

Medium
Always signal renderer readiness

If initialization or parsing throws before the last line, signalReady() is never
called and the Screenly renderer can remain stuck waiting. Wrap the setup in
try/finally so readiness is always signaled even when rendering fails.

edge-apps/simple-table-app/src/main.ts [48-67]

 document.addEventListener('DOMContentLoaded', () => {
   setupErrorHandling()
-  setupTheme()
 
-  const csvContent = getSettingWithDefault<string>(
-    'content',
-    'Name,Age\nJohn,25\nJane,30',
-  )
-  const title = getSettingWithDefault<string>('title', '')
+  try {
+    setupTheme()
 
-  const titleEl = document.getElementById('table-title')
-  if (titleEl && title) {
-    titleEl.textContent = title
-    titleEl.hidden = false
+    const csvContent = getSettingWithDefault<string>(
+      'content',
+      'Name,Age\nJohn,25\nJane,30',
+    )
+    const title = getSettingWithDefault<string>('title', '')
+
+    const titleEl = document.getElementById('table-title')
+    if (titleEl && title) {
+      titleEl.textContent = title
+      titleEl.hidden = false
+    }
+
+    renderTable(csvContent)
+  } finally {
+    signalReady()
   }
-
-  renderTable(csvContent)
-
-  signalReady()
 })
Suggestion importance[1-10]: 6

__

Why: Moving signalReady() into a finally block makes the Screenly lifecycle more robust if setupTheme(), getSettingWithDefault(), or renderTable() throws. This is a useful failure-path improvement, though its impact is limited to error scenarios and the proposed change does not cover failures before the try block.

Low

Previous suggestions

Suggestions up to commit 899c5b5
CategorySuggestion                                                                                                                                    Impact
Possible issue
Handle quoted CSV correctly

parseCSV currently splits on every comma and newline, which corrupts valid CSV
values such as quoted fields containing commas, line breaks, or escaped quotes.
Parse the string statefully so quoted content stays in the same cell instead of
shifting columns or rows.

edge-apps/simple-table-app/src/main.ts [10-16]

 function parseCSV(csv: string): string[][] {
-  return csv
-    .trim()
-    .split('\n')
-    .map((row) => row.split(',').map((cell) => cell.trim()))
-    .filter((row) => row.length > 0 && row.some((cell) => cell !== ''))
+  const rows: string[][] = []
+  let currentRow: string[] = []
+  let currentCell = ''
+  let inQuotes = false
+
+  for (let i = 0; i < csv.length; i++) {
+    const char = csv[i]
+    const nextChar = csv[i + 1]
+
+    if (char === '"') {
+      if (inQuotes && nextChar === '"') {
+        currentCell += '"'
+        i++
+      } else {
+        inQuotes = !inQuotes
+      }
+      continue
+    }
+
+    if (!inQuotes && char === ',') {
+      currentRow.push(currentCell)
+      currentCell = ''
+      continue
+    }
+
+    if (!inQuotes && (char === '\n' || char === '\r')) {
+      if (char === '\r' && nextChar === '\n') i++
+      currentRow.push(currentCell)
+      if (currentRow.some((cell) => cell.trim() !== '')) {
+        rows.push(currentRow)
+      }
+      currentRow = []
+      currentCell = ''
+      continue
+    }
+
+    currentCell += char
+  }
+
+  currentRow.push(currentCell)
+  if (currentRow.some((cell) => cell.trim() !== '')) {
+    rows.push(currentRow)
+  }
+
+  return rows
 }
Suggestion importance[1-10]: 8

__

Why: The current parseCSV implementation in src/main.ts breaks valid CSV with quoted commas, embedded newlines, or escaped quotes, which can corrupt table structure. This is a real correctness issue for user-provided CSV content, and the suggested stateful parser addresses it directly.

Medium
Avoid double-escaping displayed values

textContent already escapes HTML, so calling escapeHtml first will render entities
like < and & literally in the table. Write the raw cell values to textContent
to avoid corrupting user-provided data.

edge-apps/simple-table-app/src/main.ts [38-50]

 headers.forEach((header) => {
   const th = document.createElement('th')
-  th.textContent = escapeHtml(header)
+  th.textContent = header
   headerRow.appendChild(th)
 })
 ...
 row.forEach((cell) => {
   const td = document.createElement('td')
-  td.textContent = escapeHtml(cell)
+  td.textContent = cell
   tr.appendChild(td)
 })
Suggestion importance[1-10]: 7

__

Why: Writing escapeHtml(...) into textContent causes values like <tag> or & to be displayed as literal entities because textContent already treats input as plain text. This is an accurate fix for a user-visible rendering bug in the table output.

Medium

- Add icon.svg to static/images/
- Fix icon URL to point to correct path
- Add entrypoint type to manifest files
- Replace flex-start alignment and top padding with center alignment
- Add screenshots for supported resolutions
- Remove simple-table-app-old (Vue/Pinia/PapaParse implementation)
- Update README to match structure of other Edge Apps
@nicomiguelino nicomiguelino marked this pull request as ready for review March 26, 2026 22:24
- Remove local escapeHtml implementation
- Import escapeHtml from @screenly/edge-apps
@github-actions
Copy link

Persistent review updated to latest commit 63d1773

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes edge-apps/simple-table-app by replacing the Vue/SCSS implementation with a plain HTML/CSS/TypeScript app using the shared @screenly/edge-apps library/components, and adds screenshot automation assets.

Changes:

  • Replaced the Vue app (components/stores/unit tests) with a DOM-based TypeScript renderer and a new glassmorphism CSS theme.
  • Switched build/dev/lint/type-check workflows to the shared edge-apps-scripts tooling and removed app-local Vite/Vitest/ESLint configs.
  • Added/updated manifests and screenshot automation + committed updated screenshot outputs.

Reviewed changes

Copilot reviewed 28 out of 43 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
edge-apps/simple-table-app/vitest.config.ts Removed app-local Vitest config (now handled by shared tooling).
edge-apps/simple-table-app/vite.config.ts Removed app-local Vite config (now handled by shared tooling).
edge-apps/simple-table-app/tsconfig.vitest.json Removed Vitest-specific TS config.
edge-apps/simple-table-app/tsconfig.node.json Removed Node/tooling TS config.
edge-apps/simple-table-app/tsconfig.json Removed TS project references config.
edge-apps/simple-table-app/tsconfig.app.json Removed Vue/DOM TS config.
edge-apps/simple-table-app/src/test-setup.ts Removed Vitest global screenly mock setup.
edge-apps/simple-table-app/src/stores/settings.ts Removed Pinia/Vue settings store.
edge-apps/simple-table-app/src/main.ts New TS bootstrap: reads settings + renders table into the DOM.
edge-apps/simple-table-app/src/css/style.css New dark “glass” table styling using Screenly design system tokens.
edge-apps/simple-table-app/src/components/tests/App.spec.ts Removed Vue unit test.
edge-apps/simple-table-app/src/components/TimeDisplay.vue Removed Vue time component (replaced by shared <app-header>).
edge-apps/simple-table-app/src/components/TableDisplay.vue Removed Vue table component (replaced by DOM rendering).
edge-apps/simple-table-app/src/assets/time-display.scss Removed legacy SCSS for time display.
edge-apps/simple-table-app/src/assets/table-display.scss Removed legacy SCSS for table display.
edge-apps/simple-table-app/src/assets/main.scss Removed legacy SCSS entry.
edge-apps/simple-table-app/src/App.vue Removed Vue app root component.
edge-apps/simple-table-app/playwright.config.ts Removed app-local Playwright config (now handled by shared tooling).
edge-apps/simple-table-app/package.json Switched to shared scripts/tooling + updated deps for Bun/Playwright workflow.
edge-apps/simple-table-app/index.html New plain HTML shell using <auto-scaler> + shared <app-header> + table scaffolding.
edge-apps/simple-table-app/eslint.config.ts Removed app-local ESLint config (now handled by shared tooling).
edge-apps/simple-table-app/e2e/vue.spec.ts Removed legacy Playwright smoke test targeting the Vue build.
edge-apps/simple-table-app/e2e/tsconfig.json Removed e2e TS config.
edge-apps/simple-table-app/e2e/screenshots.spec.ts Added Playwright-driven screenshot capture across supported resolutions.
edge-apps/simple-table-app/README.md Updated docs for the new plain HTML/TS workflow and scripts.
edge-apps/simple-table-app/.vscode/extensions.json Removed Vue/Vitest extension recommendations.
edge-apps/simple-table-app/.ignore Added packaging ignore for node_modules/.
edge-apps/simple-table-app/.gitignore Added ignores for build output/logs/PNG screenshot artifacts.
edge-apps/simple-table-app/screenly.yml Manifest for the new app settings/metadata.
edge-apps/simple-table-app/screenly_qc.yml QC manifest synced with the new app settings/metadata.
edge-apps/simple-table-app/screenshots/800x480.webp Added/updated screenshot artifact.
edge-apps/simple-table-app/screenshots/480x800.webp Added/updated screenshot artifact.
edge-apps/simple-table-app/screenshots/720x1280.webp Added/updated screenshot artifact.
edge-apps/simple-table-app/screenshots/1280x720.webp Added/updated screenshot artifact.
edge-apps/simple-table-app/screenshots/1920x1080.webp Added/updated screenshot artifact.
edge-apps/simple-table-app/screenshots/1080x1920.webp Added/updated screenshot artifact.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


screenly.yml # Edge App configuration
```bash
bun test
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README suggests running bun test, but this repo’s edge-app packages typically document bun run test:unit (matching the package scripts and flags like --pass-with-no-tests). Consider updating the command here to reference the script so contributors run tests consistently.

Suggested change
bun test
bun run test:unit

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +33
const th = document.createElement('th')
th.textContent = escapeHtml(header)
headerRow.appendChild(th)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escapeHtml() returns an HTML-escaped string, but the value is assigned via textContent, which already renders text safely. This will double-escape user input (e.g., < becomes &lt; on screen). Use textContent = header (or switch to innerHTML if you truly need pre-escaped HTML).

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +42
const td = document.createElement('td')
td.textContent = escapeHtml(cell)
tr.appendChild(td)
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the header cells: using escapeHtml() with textContent will show escaped entities to users. Prefer assigning the raw value to textContent (or use innerHTML with escaping, but not both).

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +16
function parseCSV(csv: string): string[][] {
return csv
.trim()
.split('\n')
.map((row) => row.split(',').map((cell) => cell.trim()))
.filter((row) => row.length > 0 && row.some((cell) => cell !== ''))
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseCSV is not CSV-compliant (it splits on every , and \n), so quoted fields like "Last, First" or fields containing newlines will be parsed incorrectly. Since the setting is described as CSV, consider using a real CSV parser (e.g., papaparse) or implementing minimal RFC4180-style quote handling.

Copilot uses AI. Check for mistakes.
"@playwright/test": "^1.58.0",
"@screenly/edge-apps": "workspace:../edge-apps-library",
"@types/bun": "^1.3.9",
"@types/jsdom": "^27.0.0",
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdom is pinned to ^28.1.0, but @types/jsdom is set to ^27.0.0 here. This mismatch can cause incorrect typings; other edge-apps packages in this repo use @types/jsdom ^28.0.0 alongside jsdom 28.x, so aligning the version would avoid type drift.

Suggested change
"@types/jsdom": "^27.0.0",
"@types/jsdom": "^28.0.0",

Copilot uses AI. Check for mistakes.
display_errors: 'false',
message: 'Hello, World!',
override_locale: 'en',
override_timezone: 'America/New_York',
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The screenshot mock settings don’t include the app’s primary inputs (content and optional title), so the generated screenshots won’t exercise the CSV parsing/rendering logic beyond the hardcoded defaults. Setting content (and optionally title) in the mock would make these screenshots a better regression test for the table renderer.

Suggested change
override_timezone: 'America/New_York',
override_timezone: 'America/New_York',
title: 'Sample Table',
content: 'Name,Age\nAlice,30\nBob,25',

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants