Skip to content

Club: add a camera the catalogue does not have, with its photos - #400

Merged
openipc-ai merged 2 commits into
masterfrom
club-new-camera
Oct 6, 2026
Merged

openipc-ai merged 2 commits into
masterfrom
club-new-camera

Conversation

@openipc-ai

Copy link
Copy Markdown
Collaborator

A camera owner can now add a camera the catalogue does not have. They send its maker, its board's marking or model, and photos of it; ipctool is not needed. Publishing the report adds the board to the catalogue, and the member earns stars as usual.

Why

Until now a member could send photos only from an existing board's panel. A camera the catalogue lacked needed ipctool's output, and even then nothing could add it to the catalogue: only the importers create boards.

Sending

  • /cameras/report#new has a new form, club/NewCameraForm. It asks for the maker, the marking or model, and at least one photo. The SoC, ipctool's output and a boot log are optional.
  • It posts to POST /api/v1/club/reports with channel=web, sending maker, board and soc instead of model. The rules are in reports.proposalOf:
    • at least one photo;
    • both the maker and the board;
    • only on the web channel;
    • never together with model;
    • a raw flash dump still needs a known board.
  • The proposal is kept as sent in report_proposals (migration 028), which is insert-only and guarded like the other owner-report tables.
  • Links to the form:
    • the catalogue's empty filter and empty search results ("Not in the catalogue? Add your camera");
    • "My submissions" on /club, where a pending report is shown as "New: Maker Board".

Reviewing

  • /club/review shows the proposal and the board that publishing would add (boards.Suggest):
    • the catalogue's own maker when the name or an alias matches;
    • ids made the way the importers make them;
    • the board that already answers to the marking, if one does. In that case the page says so and publishes there by default.
  • The reviewer can edit the maker name and id, the marking, the board id and the SoC.
  • Publishing with new_board (boards.CreateModel):
    • adds the maker when it is new, and the board;
    • stores the SoC slug when the catalogue knows the SoC, and always keeps the label;
    • records the marking as an alias with source club, so a later import of the same board joins it instead of adding a twin;
    • refuses a board id or a marking the catalogue already has.
  • The command line does the same: openipc reports publish <id> --new-board.
  • Decide now adds the board, links the report and records the review in one transaction. A refused board, or a link to a board that does not exist, leaves nothing behind. Before this, a failed link kept the earlier links in a multi-board publish.
  • The web role loads data/catalogue to resolve SoCs. If loading fails, it logs a warning and keeps only the label.

Stars are unchanged: 1 per photo and file, once published.

Tests

  • service/run.sh test: all 22 packages pass. New tests:
    • reports: what a proposal is accepted or refused with; a proposal cannot be changed after it is stored.
    • club: send, queue suggestion, a refused twin board that leaves nothing behind, publish, stars, the photos listed for the board's unit, and a second owner linked to the existing board.
    • boards: maker matching, refusing a twin, a later import joining the board, and refusing bad ids.
    • deploytest: report_proposals is now one of the tables only internal/reports may touch.
  • frontend (node:24): lint and typecheck are clean, and all 673 tests pass. The new NewCamera.test.tsx covers what the form posts and the review page publishing a corrected board or an existing one.
  • deploy/static/build.sh and check-bundle.sh pass: 698 files checked against reserved-paths.

Not yet done

  • Validation on dev.openipc.org: send a test camera with two photos, publish it from /club/review, then check that the board appears in /cameras/boards with the photos and that /club shows +2 ★.

A member could send photos only from an existing board's panel; a camera
the catalogue lacked needed ipctool's output, and even then nothing could
add it: only the importers make boards.

Sending: /cameras/report#new takes the camera's maker, its board's marking
or model, at least one photo, and optionally the SoC, ipctool's output and
a boot log (POST /api/v1/club/reports, channel web, maker/board/soc in place
of model). The proposal is kept as sent in report_proposals (migration 028),
guarded like the other owner-report tables. The catalogue's empty searches
and /club's My submissions link to the form.

Reviewing: the queue shows the proposal and the board publishing would add
(boards.Suggest): the catalogue's own maker when the name or an alias
matches, ids made the way the importers make them, and the board that
already answers to the marking, if one does. The reviewer corrects any of
it; publishing with new_board adds the maker and the board
(boards.CreateModel), with an alias of source "club" so a later archive
import joins it instead of adding a twin, and refuses a board id or marking
the catalogue already has. `openipc reports publish <id> --new-board` does
the same.

Decide now adds the board, links the report and records the review in one
transaction, so a refused board or a link to a missing one leaves nothing
behind -- before, a failed link left earlier links in place.

Stars are unchanged: 1 per photo and file, once published.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Let Club members submit and publish cameras missing from the catalogue

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Let owners submit a missing camera’s maker, marking and photos without ipctool.
• Let maintainers correct proposals and publish reports onto new or existing boards.
• Keep proposals immutable and make board creation, report links and review decisions atomic.
Diagram

graph TD
  Form["New camera form"] --> API["Club API"] --> Store["Report store"] --> Proposals[("Report proposals")]
  Review["Review page"] --> API
  Store --> Boards["Board creation"] --> Catalogue[("Board catalogue")]
  Store --> Stars[("Stars ledger")]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Route reviewed proposals through the board importer
  • ➕ Could reuse importer-owned board creation and alias conventions.
  • ➖ Importer inputs represent external snapshots, not an individual review decision.
  • ➖ Would make atomic board creation and report publication harder to preserve.

Recommendation: Keep the dedicated transactional board-creation path. It fits reviewer-corrected proposals while sharing SoC and alias conventions with the importer; routing a review through snapshot import would add a mismatched intermediate workflow.

Files changed (30) +1123 / -67

Enhancement (13) +572 / -56
Boards.tsxLink empty catalogue results to camera submission +12/-6

Link empty catalogue results to camera submission

• Adds a locale-aware link to the new-camera form when board filters or search return no results.

frontend/apps/site/src/components/boards/Boards.tsx

Club.tsxSurface new submissions in the member ledger +7/-2

Surface new submissions in the member ledger

• Links My submissions to the new-camera form and labels reports awaiting a board as proposed cameras.

frontend/apps/site/src/components/club/Club.tsx

NewCameraForm.tsxAdd the photo-based new-camera form +118/-0

Add the photo-based new-camera form

• Collects a maker, marking and photos, with optional SoC, ipctool output and boot log. Sends a web-channel report and shows the existing member or guest receipt flow.

frontend/apps/site/src/components/club/NewCameraForm.tsx

Review.tsxLet reviewers correct and publish proposed boards +44/-6

Let reviewers correct and publish proposed boards

• Shows the sender’s proposal and catalogue suggestion, including an existing-board warning. Reviewers can edit board details, create a board, or publish onto an existing one.

frontend/apps/site/src/components/club/Review.tsx

Report.astroMount the new-camera form on the report page +10/-0

Mount the new-camera form on the report page

• Adds the '#new' section alongside the existing camera-report instructions.

frontend/apps/site/src/components/pages/Report.astro

club.tsType proposal and new-board review responses +19/-2

Type proposal and new-board review responses

• Adds proposal and suggestion types to member and queue records, and allows review requests to include a new board.

frontend/apps/site/src/lib/club.ts

reports.goSupport publishing proposed boards from the CLI +46/-3

Support publishing proposed boards from the CLI

• Adds 'reports publish --new-board', using the same board suggestion and SoC resolution as web review. Directs operators to an existing board when the marking already matches one.

service/cmd/openipc/reports.go

newmodel.goSuggest and create reviewed catalogue boards +141/-0

Suggest and create reviewed catalogue boards

• Matches known makers, derives importer-style IDs and detects existing markings. Validates and creates makers, boards and Club-sourced aliases, preserving the SoC label and resolving its slug when possible.

service/internal/boards/newmodel.go

api.goAccept new-board decisions in the review API +12/-8

Accept new-board decisions in the review API

• Passes an optional board definition to report publication and returns the created board ID to reviewers.

service/internal/club/api.go

028_report_proposals.sqlPersist immutable sender proposals +21/-0

Persist immutable sender proposals

• Adds 'report_proposals' with maker, marking and SoC constraints. Report guards prevent updates, deletes and truncation.

service/internal/db/migrations/028_report_proposals.sql

club.goPublish boards, report links and reviews atomically +67/-22

Publish boards, report links and reviews atomically

• Adds proposal and board suggestions to member and review records. Creates a requested board, links report models and records the review in one transaction so failed decisions leave no partial catalogue or link changes.

service/internal/reports/club.go

handler.goAccept photo-backed proposals without ipctool +46/-7

Accept photo-backed proposals without ipctool

• Recognises maker, board and SoC form fields and validates that a web-channel proposal has both names and at least one photo. Keeps catalogue-board submissions separate and exposes SoC resolution to the report store.

service/internal/reports/handler.go

store.goStore and retrieve original camera proposals +29/-0

Store and retrieve original camera proposals

• Inserts each sender proposal with its report and retrieves it for member and reviewer views. Carries an optional SoC resolver for board creation.

service/internal/reports/store.go

Refactor (1) +1 / -8
snapshot.goShare SoC normalisation with reviewed boards +1/-8

Share SoC normalisation with reviewed boards

• Makes snapshot imports use the same SoC-to-slug helper as boards created during review.

service/internal/boards/snapshot.go

Tests (5) +376 / -2
NewCamera.test.tsxTest camera submission and reviewer publication payloads +109/-0

Test camera submission and reviewer publication payloads

• Checks required photos, submitted form fields, corrected new-board publication and publication onto an existing board.

frontend/apps/site/src/components/club/NewCamera.test.tsx

reports_test.goInclude proposals in report-table ownership checks +2/-2

Include proposals in report-table ownership checks

• Extends the deployment guard test so other packages cannot directly touch 'report_proposals'.

service/deploytest/reports_test.go

newmodel_test.goTest board suggestions, duplicates and importer reuse +97/-0

Test board suggestions, duplicates and importer reuse

• Covers maker matching, new-maker creation, invalid IDs, duplicate markings and later imports joining a Club-created board.

service/internal/boards/newmodel_test.go

newcamera_test.goTest the complete missing-camera Club workflow +106/-0

Test the complete missing-camera Club workflow

• Exercises submission, queue suggestions, failed and successful reviews, stars, published photos and a second owner linking to the existing board.

service/internal/club/newcamera_test.go

api_test.goTest proposal acceptance and upload restrictions +62/-0

Test proposal acceptance and upload restrictions

• Covers photo-only proposals, immutable storage, optional ipctool output and rejection of missing fields, wrong channels, mixed model fields, oversized values and raw flash dumps.

service/internal/reports/api_test.go

Documentation (10) +167 / -1
CLAUDE.mdDocument the proposal-to-board workflow +5/-1

Document the proposal-to-board workflow

• Explains how photo-based camera proposals become catalogue boards when a maintainer publishes their reports.

CLAUDE.md

boards.en.ymlAdd English copy for new-camera submission and review +27/-0

Add English copy for new-camera submission and review

• Adds catalogue links, form guidance, proposal labels, reviewer controls and publication messages.

data/locales/boards.en.yml

boards.ru.ymlAdd Russian copy for new-camera submission and review +27/-0

Add Russian copy for new-camera submission and review

• Localises the new form, catalogue entry points, proposal display and reviewer workflow.

data/locales/boards.ru.yml

boards.zh.ymlAdd Chinese copy for new-camera submission and review +27/-0

Add Chinese copy for new-camera submission and review

• Localises the new form, catalogue entry points, proposal display and reviewer workflow.

data/locales/boards.zh.yml

boards.en.jsonExpose English board and Club UI strings +26/-0

Expose English board and Club UI strings

• Adds runtime translations for the form, empty-result links, proposals and review decisions.

frontend/apps/site/src/i18n/boards.en.json

boards.ru.jsonExpose Russian board and Club UI strings +26/-0

Expose Russian board and Club UI strings

• Adds runtime translations for the form, empty-result links, proposals and review decisions.

frontend/apps/site/src/i18n/boards.ru.json

boards.zh.jsonExpose Chinese board and Club UI strings +26/-0

Expose Chinese board and Club UI strings

• Adds runtime translations for the form, empty-result links, proposals and review decisions.

frontend/apps/site/src/i18n/boards.zh.json

en.jsonLabel the English photo-submission section +1/-0

Label the English photo-submission section

• Adds the heading for submitting a camera without shell access.

frontend/apps/site/src/i18n/en.json

ru.jsonLabel the Russian photo-submission section +1/-0

Label the Russian photo-submission section

• Adds the localised heading for submitting a camera without shell access.

frontend/apps/site/src/i18n/ru.json

zh.jsonLabel the Chinese photo-submission section +1/-0

Label the Chinese photo-submission section

• Adds the localised heading for submitting a camera without shell access.

frontend/apps/site/src/i18n/zh.json

Other (1) +7 / -0
main.goLoad catalogue SoC resolution for web reviews +7/-0

Load catalogue SoC resolution for web reviews

• Wires the catalogue resolver into report reviews. If the catalogue cannot load, board creation retains the supplied SoC label and logs a warning.

service/cmd/openipc/main.go

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

qodo-free-for-open-source-projects Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Published boot logs expose camera IDs ✓ Resolved
Description
Submit skips parsing when a new-camera proposal has no YAML, leaving facts empty before it
creates public copies with Redact. If the optional boot log or note contains a camera identifier,
redaction makes no replacement, and the text becomes available when the report is published.
Code

service/internal/reports/handler.go[191]

+	if in.yaml != "" || model == "" && prop == nil {
Evidence
Rule 7 requires public copies to replace identifiers with keyed hashes. The new proposal path
permits a photo without YAML; on that path parsing is skipped, while public notes and text files
still pass through Redact, which replaces only identifiers present in facts. Published reports
expose those public copies.

CLAUDE.md: Protect Owner Reports and Public Copies: CLAUDE.md: Protect Owner Reports and Public Copies: CLAUDE.md: Protect Owner Reports and Public Copies: CLAUDE.md: Protect Owner Reports and Public Copies
service/internal/reports/handler.go[190-216]
service/internal/reports/handler.go[231-247]
service/internal/reports/parse.go[305-315]
service/internal/reports/view.go[60-87]

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

## Issue description
Photo-only new-camera proposals can include notes or boot logs containing camera identifiers, but their public copies are built with empty identifier facts when YAML is absent.
## Fix Focus Areas
- service/internal/reports/handler.go[190-216]
- service/internal/reports/handler.go[231-247]
## Recommended Fix
Extract and hash identifiers from optional text before making its public copy, even when the proposal has no YAML. If an identifier cannot be safely redacted, do not publish that text.

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


2. A failed award leaves a board published ✓ Resolved
Description
Decide commits the new board, report link and review before starting a separate transaction for
the member’s stars. If that second transaction fails or its context is cancelled, the review
endpoint returns an error even though the board is already in the catalogue; retrying with the same
new-board details then fails because its ID is taken.
Code

service/internal/reports/club.go[R370-372]

+	err = pgx.BeginFunc(ctx, s.DB, func(tx pgx.Tx) error {
+		if newBoard != nil {
+			id, err := boards.CreateModel(ctx, tx, *newBoard, s.SoC)
Evidence
The first transaction commits at the end of the changed block; star reconciliation starts afterward
and can return an error. The API returns that error without running its successful-review follow-up,
while CreateModel refuses the ID on a retry.

service/internal/reports/club.go[370-395]
service/internal/reports/club.go[399-455]
service/internal/club/api.go[615-627]
service/internal/boards/newmodel.go[107-116]

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

## Issue description
A failed star-award transaction can leave a newly created board and published review committed while the reviewer receives an error.
## Fix Focus Areas
- service/internal/reports/club.go[370-455]
- service/internal/club/api.go[615-627]
## Recommended Fix
Perform board creation, linking, review insertion and star reconciliation in one transaction, preserving the report-level locking needed for award calculations.

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


3. Rejecting a report links it to boards ✓ Resolved
Description
Store.Decide inserts every supplied model into report_models without checking whether the
decision is publish, whereas the previous link operation ran only for publish decisions. The
review page sends its prefilled board IDs with Reject, so rejecting a typical report links it to a
catalogue board; the link requires openipc reports unlink to remove and can place the report’s
files on that board if the report is later republished.
Code

service/internal/reports/club.go[R378-380]

+		for _, m := range models {
+			if _, err := tx.Exec(ctx, `INSERT INTO report_models (report_id, model_id, by) VALUES ($1,$2,$3)
+				ON CONFLICT DO NOTHING`, id, m, by); err != nil {
Evidence
The previous code called s.Link only when decision == "publish", but the new transaction runs
its for _, m := range models insertion loop for either decision; only the hint lookup and
new-board check depend on publish. Review.tsx initializes the board-ids input from q.models or
the suggested existing board, then passes its IDs to decide for both Publish and Reject.
Published board text reads retained links, while removing one requires Unlink through the guarded
Unguarded path.

service/internal/reports/club.go[347-353]
service/internal/reports/club.go[370-391]
service/internal/reports/store.go[299-317]
frontend/apps/site/src/components/club/Review.tsx[69-69]
frontend/apps/site/src/components/club/Review.tsx[83-84]
frontend/apps/site/src/components/club/Review.tsx[68-84]
service/internal/reports/club.go[378-390]
service/internal/reports/club.go[477-490]

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

## Issue description
`Store.Decide` inserts supplied `report_models` links for reject decisions. Because the review page submits prefilled board IDs on Reject, rejecting a report can leave it linked to a board.
## Fix Focus Areas
- service/internal/reports/club.go[370-391]
- frontend/apps/site/src/components/club/Review.tsx[68-84]
## Recommended Fix
Run the report-model insertion loop only when `decision == "publish"` (or clear `models` at the start of `Decide` for a reject). Have the frontend send an empty IDs list on Reject. Add a test that rejects a proposal with a prefilled existing board or supplied models and verifies that no `report_models` row is created and its links remain unchanged.

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



Remediation recommended

4. An archive can add a twin camera board ✓ Resolved
Description
CreateModel records the reviewed marking as an alias, but the OpenHisi archive importer never
consults that alias before inserting its own model ID. If a reviewer corrects the new board’s ID
away from the archive-derived ID, a later import of the same maker and marking creates a second
board and attaches the archive unit to it.
Code

service/internal/boards/newmodel.go[R124-126]

+	if _, err := tx.Exec(ctx, `INSERT INTO board_model_aliases (maker_id, code_norm, model_id, source, code_as_printed)
+		VALUES ($1, $2, $3, 'club', $4)`, m.MakerID, code, m.ModelID, m.Model); err != nil {
+		return "", err
Evidence
The parser independently derives an ID; the archive importer ignores model and alias conflicts and
attaches units using that derived ID. Unlike the snapshot importer, it does not resolve the new club
alias.

service/internal/boards/newmodel.go[124-126]
service/internal/boards/parse.go[207-221]
service/internal/boards/import.go[230-266]
service/internal/boards/snapshot.go[424-432]

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 OpenHisi archive import path ignores club aliases and can create a second board when a reviewer chose a corrected ID.
## Fix Focus Areas
- service/internal/boards/import.go[230-266]
- service/internal/boards/parse.go[207-221]
- service/internal/boards/newmodel.go[124-126]
## Recommended Fix
Before saving an archive model and its units, resolve its normalized maker/marking through the alias index and use the existing model ID when found; test a club board with a corrected ID.

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


5. New makers block later snapshot imports ✓ Resolved
Description
CreateModel can add a maker to board_manufacturers, but the snapshot importer validates makers
against its hard-coded map rather than that table. A later donor snapshot naming that newly added
maker fails with unknown maker before it can use the club board’s alias.
Code

service/internal/boards/newmodel.go[R102-104]

+	if _, err := tx.Exec(ctx, `INSERT INTO board_manufacturers (id, name, position)
+		VALUES ($1, $2, (SELECT coalesce(max(position), 0) + 1 FROM board_manufacturers)) ON CONFLICT (id) DO NOTHING`,
+		m.MakerID, m.MakerName); err != nil {
Evidence
The new creation path inserts makers into the database. The snapshot path instead calls
makersByID, built solely from static definitions, and errors before alias resolution when the
maker is absent there.

service/internal/boards/newmodel.go[102-104]
service/internal/boards/snapshot.go[441-447]
service/internal/boards/snapshot.go[698-703]

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

## Issue description
Snapshot imports cannot use makers first added by publishing a club report because maker validation checks only a static map.
## Fix Focus Areas
- service/internal/boards/snapshot.go[441-447]
- service/internal/boards/snapshot.go[698-703]
- service/internal/boards/newmodel.go[102-104]
## Recommended Fix
Allow snapshot import to resolve a maker from the catalogue when it is absent from the static definitions, then test a snapshot joining a board under a club-created maker.

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


6. Some accepted cameras cannot publish ✓ Resolved
Description
Suggest forms the default board ID by concatenating the maker ID and marking without fitting it to
CreateModel’s 128-character ID limit. A valid proposal with a 64-character maker ID and
64-character marking therefore reaches the review queue with a 129-character suggested ID, which
publication rejects unless the reviewer manually replaces it.
Code

service/internal/boards/newmodel.go[68]

+	s.ModelID = slug(s.MakerID + "-" + s.Model)
Evidence
The proposal schema permits each input at this length, while the constructed ID includes both inputs
and a separator; CreateModel applies a stricter limit only when the reviewer publishes.

service/internal/db/migrations/028_report_proposals.sql[11-16]
service/internal/boards/newmodel.go[55-72]
service/internal/boards/newmodel.go[87-90]
service/internal/reports/club.go[370-375]

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

## Issue description
Accepted maker and marking lengths can produce a suggested board ID that publication always rejects.
## Fix Focus Areas
- service/internal/boards/newmodel.go[55-72]
- service/internal/boards/newmodel.go[87-90]
- service/internal/db/migrations/028_report_proposals.sql[11-16]
## Recommended Fix
Generate a valid bounded default ID for all accepted proposals, with a deterministic way to retain uniqueness, and test long maker-plus-marking combinations.

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


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread service/internal/reports/handler.go
Comment thread service/internal/reports/club.go Outdated
Comment thread service/internal/boards/newmodel.go
Comment thread service/internal/boards/newmodel.go
Comment thread service/internal/boards/newmodel.go Outdated
Comment thread service/internal/reports/club.go Outdated
…n, link only on publish, importers join club boards

- Redact replaces any MAC written as six colon- or hyphen-separated pairs,
  not only the one ipctool named: a boot log or U-Boot environment sent
  without ipctool's output (to a catalogue board, or with a new camera's
  photos) was published with its ethaddr.
- Decide writes the new board, the links, the review and the sender's
  stars in one transaction: a ledger that cannot be written no longer
  leaves the board published and the decision unrepeatable.
- Rejecting links nothing again; the links had moved outside the publish
  branch.
- The archive importer puts a unit on the board a review added under the
  reviewer's id (alias source club) instead of making a twin; the snapshot
  importer accepts a maker a review added; Suggest knows the importers'
  makers and cuts the ids it makes to their lengths.
- NewCamera.test.tsx: its field helper takes an Element (typecheck).
@openipc-ai
openipc-ai merged commit 13c23da into master Oct 6, 2026
4 checks passed
@openipc-ai
openipc-ai deleted the club-new-camera branch October 6, 2026 10:45
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