Skip to content

Add the pages the relaunch is for, without pointing anyone at them yet - #123

Open
openipc-ai wants to merge 1 commit into
feat/layout-partialsfrom
feat/new-pages
Open

Add the pages the relaunch is for, without pointing anyone at them yet#123
openipc-ai wants to merge 1 commit into
feat/layout-partialsfrom
feat/new-pages

Conversation

@openipc-ai

Copy link
Copy Markdown
Collaborator

Fourth of five PRs rebasing the June 11 relaunch onto master.
Stacked on #122 (which is stacked on #121). Merge in order and GitHub
retargets each one.

Seven pages — home, get-started, low-latency, ecosystem, business, community,
donate — plus the shared partials they're built from. They answer on their own
URLs from this commit, but the root route and navigation still serve the
pre-relaunch structure
, so nothing a visitor sees changes. The cutover is #5.
This one can go to dev.openipc.org and be looked at first.

Things the draft got wrong, that the rendering tests caught

/get-started raised on render. It hardcoded four WebUI screenshots by
filename — webui/preview.jpg and three others. The gallery was reshot at twice
the resolution in 0f42514 and every one of those files stopped existing. It now
takes them from WebuiGallery — the same manifest /web-interface reads and
tools/webui-gallery photographs from — so the two cannot disagree about what
exists. Tile and full-resolution copy are passed as a pair, which is what
data-zoom is for.

Four links to the retired wiki host. Every wiki.openipc.org link in the new
copy now points at github.com/OpenIPC/wiki. 2842ffa and 73c0961 removed the
last references to that host; this copy would have put four back.

The partner wall was ten entries out of date. It moves out of the markup into
PagesHelper, transcribed from /introduction as it stands today. Entries
that page has commented out are commented out here too, with their URLs, so
nothing appears or disappears silently when /introduction goes.

⚠️ One content decision for you

The Russian integrator block is currently commented out on /introduction,
so no visitor sees any of it today. Rendering it for :ru brings it back. That's
what the relaunch plan asks for, and it's gated to :ru as you asked — but it's
a content call, not a technical one. Say the word if it should stay hidden.

Related: the CSS that was supposed to enforce that gate,
html:not([lang="ru"]) article.introduction .col.logo.ru, has never matched
anything
— no logo on that page carries the ru class it selects on. Having
the gate in Ruby is what makes it testable, and there's now a test.

Also here

  • /open-wall is uncommented. It was commented out in ed0e025, a bulk
    tidy-up, while five places that redirect to it were left in — twice in
    snapshots_controller, once in admin/snapshots_controller, and the
    breadcrumb on three views. All five fell through the catch-all to a 302 home.
    This exposes nothing new: resources :snapshots has served the same gallery at
    /snapshots throughout.
  • Snapshot.latest_per_camera lifts the correlated subquery out of
    SnapshotsController#index; the homepage mosaic wants the same list.
  • pages.donate changed owner. It belonged to the band rendered under every
    page; it's now the /donate page's, and the band's two keys moved to
    pages.donate_band. Otherwise lazy lookup in the partial and the new page's
    title would have fought over one key and the band would have lost its
    please_support string.

Chinese

Written for this commit and not read by a native speaker — it should be
before it's linked to. No translation API key was available, so it isn't machine
output either. i18n-tasks reports 347 keys in each of the three files.

Verification

check result
bin/rails test 272 runs, 1223 assertions, 0 failures (238 → 272)
each page × each locale rendered, checked for "translation missing"
internal links on new pages followed; required not to land on the catch-all
homepage on an empty database renders (a fresh checkout has one)
i18n-tasks missing / unused none / 102, unchanged
rubocop 53 offences across touched files, unchanged; the two new files add none

🤖 Generated with Claude Code

https://claude.ai/code/session_015hvzXBErEjEGMyRme2K3hi

Seven pages -- home, get-started, low-latency, ecosystem, business, community,
donate -- plus the shared partials they are built from. They answer on their own
URLs from this commit, but the root route and the navigation still serve the
pre-relaunch structure, so nothing a visitor sees changes. The cutover is a
separate change, and this one can go to dev.openipc.org and be looked at first.

The copy is written in all three locales the site now serves. English and
Russian are as drafted; the Chinese was written for this commit and has not been
read by a native speaker -- it should be, before it is linked to. i18n-tasks
reports 347 keys in each of the three files and no missing translations.

Not everything from the draft survived contact with the current tree, and the
rendering tests are what found each one.

/get-started hardcoded four WebUI screenshots by filename --
webui/preview.jpg and three others. The gallery was reshot at twice the
resolution in 0f42514 and every one of those files stopped existing; the page
raised on render. It now takes them from WebuiGallery, the same manifest
/web-interface reads and the same one tools/webui-gallery photographs from, so
the two cannot disagree about what exists. Tile and full-resolution copy are
passed as a pair, which is what data-zoom in src/zoom.js is for.

Every link in the new copy that pointed at wiki.openipc.org now points at
github.com/OpenIPC/wiki. That host is retired; 2842ffa and 73c0961 removed the
last references to it and this copy would have put four of them back.

The partner wall moves out of the markup into PagesHelper, transcribed from
/introduction as it stands today rather than from the June draft, which was ten
entries out of date. Entries that page has commented out are commented out here
too, with their URLs, so nothing appears or disappears silently when
/introduction goes.

  REVIEW POINT: the Russian integrator block is currently commented out on
  /introduction, so no visitor sees any of it. Rendering it for :ru brings it
  back. That is what the relaunch plan asks for and it is gated to :ru as the
  maintainers asked, but it is a content decision -- say so if it should stay
  hidden.

Worth knowing: the CSS that was supposed to enforce that gate,
`html:not([lang="ru"]) article.introduction .col.logo.ru`, has never matched
anything. No logo on that page carries the `ru` class it selects on. The gate
being in Ruby now is what makes it testable, and there is a test.

/open-wall is uncommented. It was commented out in ed0e025, a bulk tidy-up,
while five places that redirect to it were left in place -- twice in
snapshots_controller, once in admin/snapshots_controller, and the breadcrumb on
three views. All five fell through the catch-all and answered a 302 to the
homepage. This exposes nothing new: `resources :snapshots` has served the same
gallery at /snapshots throughout.

Snapshot.latest_per_camera lifts the correlated subquery out of
SnapshotsController#index, because the homepage mosaic wants the same list and
two copies of that query is one too many.

pages.donate belonged to the band rendered under every page. It is now the
/donate page's, and the band's two keys moved to pages.donate_band -- otherwise
lazy lookup in the partial and the new page's title would have fought over the
same key, and the band would have lost its please_support string.

272 runs, 1223 assertions, 0 failures. Each page is rendered in each of the
three locales and checked for "translation missing"; every internal link on
them is followed and required not to land on the catch-all; the homepage is
rendered against an empty database, which is what a fresh checkout has. rubocop
is unchanged at 53 offences across the files this touches, and the two new files
add none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hvzXBErEjEGMyRme2K3hi
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Stage seven localized relaunch pages for pre-cutover review

✨ Enhancement 🧪 Tests 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

AI Description

• Adds seven independently routable relaunch pages without changing root or navigation.
• Introduces reusable landing-page components, styling, and content across three locales.
• Reuses live gallery and snapshot data while restoring broken Open Wall routes.
Diagram

graph TD
  R["Relaunch routes"] --> C["Pages controller"] --> V["Page views"] --> P["Shared partials"] --> B["Browser output"]
  C --> D["Live data"]
  V --> L["Locale content"]
  P --> A["Styles and JS"] --> B
Loading
High-Level Assessment

The staged, server-rendered Rails approach is appropriate: it makes every page reviewable before navigation cutover, reuses manifests and model queries where drift previously caused failures, and keeps translated copy in the existing I18n system. A CMS or configuration-driven page framework would add migration and rendering complexity without clear value for seven mostly static pages.

Files changed (35) +1942 / -9

Enhancement (27) +1163 / -0
application.bootstrap.scssRegister relaunch component and homepage styles +6/-0

Register relaunch component and homepage styles

• Imports the new section, badge, terminal, logo-wall, browser-frame, and homepage styles into the application bundle.

app/assets/stylesheets/application.bootstrap.scss

_browser-frame.scssStyle framed WebUI screenshots +35/-0

Style framed WebUI screenshots

• Adds reusable browser-chrome presentation for screenshots, including a title bar and responsive image sizing.

app/assets/stylesheets/components/_browser-frame.scss

_cards.scssAdd interactive pillar and project cards +61/-0

Add interactive pillar and project cards

• Introduces relaunch card treatments, icon chips, and an accessible reduced-motion-aware live indicator while retaining existing team card styles.

app/assets/stylesheets/components/_cards.scss

_logo-wall.scssNormalize partner logo presentation +26/-0

Normalize partner logo presentation

• Adds responsive white logo tiles with grayscale-to-color hover and focus behavior.

app/assets/stylesheets/components/_logo-wall.scss

_sections.scssAdd reusable landing-page section styles +26/-0

Add reusable landing-page section styles

• Defines dark, alternate, grid-backed, and page-header sections using Bootstrap theme tokens and visible focus treatment.

app/assets/stylesheets/components/_sections.scss

_stage-badge.scssStyle textual project stage badges +43/-0

Style textual project stage badges

• Adds stage-specific badges generated from the shared color map, including dark-theme contrast adjustments.

app/assets/stylesheets/components/_stage-badge.scss

_terminal.scssStyle copyable terminal blocks +68/-0

Style copyable terminal blocks

• Creates an LTR terminal presentation with copy controls and a reduced-motion-aware cursor animation.

app/assets/stylesheets/components/_terminal.scss

_home.scssStyle the relaunch homepage sections +108/-0

Style the relaunch homepage sections

• Adds responsive hero, live snapshot mosaic, silicon vendor strip, statistics, and story-step presentation.

app/assets/stylesheets/pages/_home.scss

_openwall.scssRefine Open Wall snapshot metadata +8/-0

Refine Open Wall snapshot metadata

• Applies monospace data-plate styling to snapshot card titles.

app/assets/stylesheets/pages/_openwall.scss

pages_controller.rbServe and populate seven relaunch pages +45/-0

Serve and populate seven relaunch pages

• Adds actions and translated titles for all new pages. The staged homepage also loads recent camera snapshots and current SoC and vendor data while remaining safe on an empty database.

app/controllers/pages_controller.rb

pages_helper.rbCentralize locale-aware partner catalogs +77/-0

Centralize locale-aware partner catalogs

• Moves current international partners and Russian integrators into frozen helper catalogs. Russian integrators are included only for the Russian locale, making the territory gate explicit and testable.

app/helpers/pages_helper.rb

application.jsInitialize clipboard interactions +2/-0

Initialize clipboard interactions

• Registers the new copy-to-clipboard initializer with the existing DOM-ready startup sequence.

app/javascript/application.js

copy.jsAdd delegated copy-to-clipboard behavior +17/-0

Add delegated copy-to-clipboard behavior

• Copies text from selector-targeted elements and temporarily swaps the trigger icon to confirm success.

app/javascript/src/copy.js

business.html.erbAdd the business landing page +71/-0

Add the business landing page

• Presents commercial offerings, platform benefits, locale-aware partners, engagement steps, licensing terms, and contact options.

app/views/pages/business.html.erb

community.html.erbAdd the community landing page +56/-0

Add the community landing page

• Documents Telegram channels, joining guidance, support etiquette, contribution paths, and calls to join or fund the project.

app/views/pages/community.html.erb

donate.html.erbAdd the donation landing page +37/-0

Add the donation landing page

• Explains funding use and exposes Open Collective and TON donation paths, with a bridge to commercial support.

app/views/pages/donate.html.erb

ecosystem.html.erbAdd the ecosystem project catalog +80/-0

Add the ecosystem project catalog

• Groups core, longevity, low-latency, and engineering projects into reusable status-bearing cards with contribution links.

app/views/pages/ecosystem.html.erb

get_started.html.erbAdd the guided onboarding page +91/-0

Add the guided onboarding page

• Introduces identification, hardware selection, backup, and flashing guidance with a copyable command. WebUI screenshots come from 'WebuiGallery' as tile/full-resolution pairs to prevent stale asset references.

app/views/pages/get_started.html.erb

home.html.erbAdd the staged relaunch homepage +144/-0

Add the staged relaunch homepage

• Builds the new full-width homepage from live Open Wall snapshots, current hardware data, platform pillars, partner logos, business messaging, and reusable CTAs. Empty snapshot slots degrade to no-signal imagery.

app/views/pages/home.html.erb

low_latency.html.erbAdd the low-latency video page +90/-0

Add the low-latency video page

• Explains the transmission pipeline, FPV and robotics uses, representative latency figures, supported hardware, and community credits.

app/views/pages/low_latency.html.erb

_cta_band.html.erbAdd reusable call-to-action bands +15/-0

Add reusable call-to-action bands

• Provides a dark full-width CTA with required primary action and optional secondary action and note.

app/views/shared/_cta_band.html.erb

_logo_wall.html.erbAdd reusable partner logo walls +10/-0

Add reusable partner logo walls

• Renders helper-provided partner metadata as responsive, accessible, lazy-loaded linked logo tiles.

app/views/shared/_logo_wall.html.erb

_page_header.html.erbAdd a standard landing-page header +9/-0

Add a standard landing-page header

• Provides a reusable page title band with an optional constrained-width lead paragraph.

app/views/shared/_page_header.html.erb

_pillar_card.html.erbAdd homepage pillar cards +9/-0

Add homepage pillar cards

• Renders icon-led platform summaries with optional accent treatment and accessible stretched links.

app/views/shared/_pillar_card.html.erb

_project_card.html.erbAdd ecosystem project cards +15/-0

Add ecosystem project cards

• Renders project descriptions, optional development-stage badges, and compact GitHub repository links.

app/views/shared/_project_card.html.erb

_stage_badge.html.erbRender textual development-stage badges +3/-0

Render textual development-stage badges

• Maps stage codes to concise labels and exposes localized stage details as tooltips.

app/views/shared/_stage_badge.html.erb

_terminal.html.erbAdd copyable terminal markup +11/-0

Add copyable terminal markup

• Renders shell commands in a terminal frame with an accessible clipboard trigger tied to a required target ID.

app/views/shared/_terminal.html.erb

Bug fix (2) +26 / -4
_donate.html.erbSeparate donation-band translation scope +5/-2

Separate donation-band translation scope

• Moves the sitewide donation band's lookups to 'pages.donate_band', preventing lazy-lookup collisions with the new Donate page.

app/views/pages/_donate.html.erb

routes.rbExpose staged pages and restore Open Wall URLs +21/-2

Expose staged pages and restore Open Wall URLs

• Adds direct routes for the seven relaunch pages without changing the root route. Restores the previously disabled '/open-wall' gallery and camera routes so existing redirects and new page links resolve correctly.

config/routes.rb

Refactor (2) +19 / -5
snapshots_controller.rbReuse the shared latest-snapshot query +1/-5

Reuse the shared latest-snapshot query

• Replaces the controller's inline greatest-per-camera SQL with 'Snapshot.latest_per_camera' while preserving pagination.

app/controllers/snapshots_controller.rb

snapshot.rbExtract latest snapshot per camera query +18/-0

Extract latest snapshot per camera query

• Adds a reusable greatest-per-camera query for snapshots seen during the last day, with optional integer-sanitized limiting for homepage use.

app/models/snapshot.rb

Tests (1) +128 / -0
relaunch_pages_test.rbCover relaunch rendering, links, and locale gates +128/-0

Cover relaunch rendering, links, and locale gates

• Verifies every new page across English, Russian, and Chinese, page titles, full-width layout behavior, internal links, empty-database homepage rendering, Russian-only integrators, and partner asset existence.

test/controllers/relaunch_pages_test.rb

Documentation (3) +606 / -0
pages.en.ymlAdd English relaunch content +202/-0

Add English relaunch content

• Adds complete English copy for all seven relaunch pages and separates the global donation-band namespace from the Donate page.

config/locales/pages.en.yml

pages.ru.ymlAdd Russian relaunch content +202/-0

Add Russian relaunch content

• Adds complete Russian copy for all seven relaunch pages and the renamed donation-band translations.

config/locales/pages.ru.yml

pages.zh.ymlAdd Chinese relaunch content +202/-0

Add Chinese relaunch content

• Adds complete Chinese copy for all seven relaunch pages and the renamed donation-band translations; the PR notes this copy still needs native review.

config/locales/pages.zh.yml

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Missing metadata crashes homepage 🐞 Bug ☼ Reliability
Description
The homepage calls upcase on every snapshot's soc and sensor, but the public upload path
permits both fields to be absent and the model does not validate them. One otherwise-valid recent
upload with either value missing makes /home raise instead of rendering.
Code

app/views/pages/home.html.erb[22]

+              <span class="wall-caption"><%= snapshot.soc.upcase %> · <%= snapshot.sensor.upcase %></span>
Evidence
The new caption dereferences both nullable values. The model's validations omit soc and sensor,
the upload endpoint accepts them as optional parameters, and both database columns are nullable, so
such a snapshot can be persisted and selected for the homepage.

app/views/pages/home.html.erb[18-22]
app/models/snapshot.rb[61-64]
app/controllers/snapshots_controller.rb[92-95]
db/schema.rb[110-121]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new homepage assumes every snapshot has non-null `soc` and `sensor`, although accepted uploads may omit both. Prevent the mosaic caption from raising on valid persisted snapshots.

## Issue Context
The upload controller permits these fields but does not require them, and `Snapshot` only validates the attachment and MAC address. Use a safe fallback in the caption and add coverage for a recent snapshot missing either metadata field.

## Fix Focus Areas
- app/views/pages/home.html.erb[18-22]
- app/models/snapshot.rb[61-64]
- app/controllers/snapshots_controller.rb[92-95]
- test/controllers/relaunch_pages_test.rb[46-56]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Sensor vendors inflate statistics 🐞 Bug ≡ Correctness
Description
PagesController#home loads every Vendor, so the “Runs on silicon by” strip and chip-vendor count
include vendors that only manufacture image sensors. The model already provides Vendor.soc_vendors
specifically to restrict this list to vendors associated with SoCs.
Code

app/controllers/pages_controller.rb[63]

+    @vendor_names = Vendor.order(:name).pluck(:name)
Evidence
The controller plucks all vendor names and the view labels their count as chip vendors. Vendor has
both socs and sensors associations plus an explicit soc_vendors scope, while seed data
includes sensor manufacturers such as Aptina, OmniVision, and Sony as ordinary Vendor rows.

app/controllers/pages_controller.rb[61-64]
app/views/pages/home.html.erb[42-49]
app/views/pages/home.html.erb[101-104]
app/models/vendor.rb[3-12]
db/seeds.rb[22-33]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The homepage lists and counts every vendor record as a chip vendor, including sensor-only manufacturers. Restrict the homepage query to vendors that have SoCs.

## Issue Context
`Vendor` already defines the `soc_vendors` scope for this distinction. Apply it before ordering/plucking and add a test with a sensor-only vendor.

## Fix Focus Areas
- app/controllers/pages_controller.rb[61-64]
- app/models/vendor.rb[3-12]
- app/views/pages/home.html.erb[42-49]
- app/views/pages/home.html.erb[101-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Timestamp ties duplicate cameras 🐞 Bug ≡ Correctness
Description
The homepage asks latest_per_camera(limit: 5) for five camera tiles, but the anti-join treats
every row sharing a camera's maximum created_at as latest because it only compares timestamps with
<. Concurrent uploads can therefore let duplicate rows for one MAC consume multiple limited mosaic
slots instead of showing five distinct cameras.
Code

app/controllers/pages_controller.rb[61]

+    @wall_snapshots = Snapshot.latest_per_camera(limit: 5)
Evidence
The anti-join excludes only strictly newer timestamps, so equal maximum timestamps all survive; the
homepage then limits rows rather than distinct cameras. Snapshot timestamps have no uniqueness
constraint, and the upload interval check is performed only in model code, so equal timestamps are
not prevented at the database level.

app/models/snapshot.rb[19-30]
app/controllers/pages_controller.rb[58-64]
app/views/pages/home.html.erb[18-25]
db/schema.rb[110-127]
app/models/snapshot.rb[149-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The homepage's limited latest-per-camera query can return multiple snapshots for one camera when maximum timestamps tie. Make selection deterministic and guarantee one row per MAC address.

## Issue Context
The schema does not make `created_at` unique, and the interval validation is an application-level read-before-write check rather than a database constraint. Add an ID tie-break to the anti-join or use an equivalent deterministic grouped/window query, with a regression test for equal timestamps.

## Fix Focus Areas
- app/models/snapshot.rb[24-30]
- app/controllers/pages_controller.rb[58-64]
- app/views/pages/home.html.erb[18-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources

Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

<a href="<%= snapshot_path(snapshot) %>" class="wall-tile">
<%= image_tag snapshot.file.variant(:thumb), alt: t('.wall_snapshot_alt'),
loading: (idx.zero? ? 'eager' : 'lazy') %>
<span class="wall-caption"><%= snapshot.soc.upcase %> · <%= snapshot.sensor.upcase %></span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Missing metadata crashes homepage 🐞 Bug ☼ Reliability

The homepage calls upcase on every snapshot's soc and sensor, but the public upload path
permits both fields to be absent and the model does not validate them. One otherwise-valid recent
upload with either value missing makes /home raise instead of rendering.
Agent Prompt
## Issue description
The new homepage assumes every snapshot has non-null `soc` and `sensor`, although accepted uploads may omit both. Prevent the mosaic caption from raising on valid persisted snapshots.

## Issue Context
The upload controller permits these fields but does not require them, and `Snapshot` only validates the attachment and MAC address. Use a safe fallback in the caption and add coverage for a recent snapshot missing either metadata field.

## Fix Focus Areas
- app/views/pages/home.html.erb[18-22]
- app/models/snapshot.rb[61-64]
- app/controllers/snapshots_controller.rb[92-95]
- test/controllers/relaunch_pages_test.rb[46-56]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@meta_description = t('site.default_meta_description')
@wall_snapshots = Snapshot.latest_per_camera(limit: 5)
@soc_count = Soc.count
@vendor_names = Vendor.order(:name).pluck(:name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Sensor vendors inflate statistics 🐞 Bug ≡ Correctness

PagesController#home loads every Vendor, so the “Runs on silicon by” strip and chip-vendor count
include vendors that only manufacture image sensors. The model already provides Vendor.soc_vendors
specifically to restrict this list to vendors associated with SoCs.
Agent Prompt
## Issue description
The homepage lists and counts every vendor record as a chip vendor, including sensor-only manufacturers. Restrict the homepage query to vendors that have SoCs.

## Issue Context
`Vendor` already defines the `soc_vendors` scope for this distinction. Apply it before ordering/plucking and add a test with a sensor-only vendor.

## Fix Focus Areas
- app/controllers/pages_controller.rb[61-64]
- app/models/vendor.rb[3-12]
- app/views/pages/home.html.erb[42-49]
- app/views/pages/home.html.erb[101-104]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

def home
@page_title = t('pages.home.title')
@meta_description = t('site.default_meta_description')
@wall_snapshots = Snapshot.latest_per_camera(limit: 5)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Timestamp ties duplicate cameras 🐞 Bug ≡ Correctness

The homepage asks latest_per_camera(limit: 5) for five camera tiles, but the anti-join treats
every row sharing a camera's maximum created_at as latest because it only compares timestamps with
<. Concurrent uploads can therefore let duplicate rows for one MAC consume multiple limited mosaic
slots instead of showing five distinct cameras.
Agent Prompt
## Issue description
The homepage's limited latest-per-camera query can return multiple snapshots for one camera when maximum timestamps tie. Make selection deterministic and guarantee one row per MAC address.

## Issue Context
The schema does not make `created_at` unique, and the interval validation is an application-level read-before-write check rather than a database constraint. Add an ID tie-break to the anti-join or use an equivalent deterministic grouped/window query, with a regression test for equal timestamps.

## Fix Focus Areas
- app/models/snapshot.rb[24-30]
- app/controllers/pages_controller.rb[58-64]
- app/views/pages/home.html.erb[18-25]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant