Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A small web app for keeping track of plant propagation experiments — which batch
of plants came from where, what you tried on them, and how each attempt is doing
over time. Photos, notes, feeding dates, and a pest reference are all in one place,
and any experiment can be exported to a PDF.
You sign in with an email and password. Everything you add is private to your
account.
The app is organised in three levels:README 전문 보기 ↗A small web app for keeping track of plant propagation experiments — which batch of plants came from where, what you tried on them, and how each attempt is doing over time. Photos, notes, feeding dates, and a pest reference are all in one place, and any experiment can be exported to a PDF.
You sign in with an email and password. Everything you add is private to your account.
The app is organised in three levels:
Three ways to add a log entry, depending on how much you want to type:
After picking or taking a photo, tap Circle to drop an adjustable red ring on it — drag to position, use the slider to size it — and it's burned into the image before upload, so the marked-up photo is what lands in the log.
From an experiment you can tap Export PDF for a shareable report with the batch details, notes, and every log entry with its photos, or CSV to get the same log entries as a spreadsheet. A folder has its own CSV export covering all of its experiments in one sheet. Experiments that have been potted export more (see Exports below).
An experiment lives on one timeline in two phases:
Plants move from Phase 1 to Phase 2 through potting events. A potting is partial or total, happens on a date and creates one or more pots. Tap Pot plants on an experiment: pick the date, then add a row per pot — label, how many plants go in it (one pot can hold several, so pots ≠ plants), optional size, substrate, notes and a photo. An experiment can have many pottings over time, and it keeps running in Phase 1 for the plants that were not moved. The page shows "up to X may be ready (R2: a, S3: b)" from the latest check-in — a hint only, nothing is enforced.
On a folder, Select experiments → Pot selected experiments pots several experiments in one go: one shared date, optional shared size / substrate / notes copied into every pot row, then a section per experiment. Each experiment is saved on its own; if some fail, the rest are kept and the failed ones stay on screen for Retry failed pottings.
Once an experiment has a potting its page has two tabs. Phase 1 is the stage snapshot, the stage charts (with a dashed "Potted " line) and the Phase 1 timeline. Phase 2 lists the pots (photo, alive / in pot, size, substrate, last check-in), a Health in pots chart and the pot timeline. Every Phase 2 check-in (deaths with cause, health good / ok / poor, photo, notes, watered, fertilized) belongs to exactly one pot — add it from the pot's own buttons. Pots are never regrouped and a plant cannot come back individually: deleting the whole potting event is the undo (it goes to the bin with its pots and their check-ins). There is no "succeeded" verdict for potting; the winner callout and ranking are Phase 1 / overall-survival based and unchanged.
All of it lives in src/lib/utils/phases.ts (pure functions, worked examples in
the file's header comment). With plant_count = N:
phase1Cohort(asOf) = N − Σ pot.plant_count of pots potted on or before asOf
phase1Remaining(asOf) = phase1Cohort(asOf) − Σ Phase 1 deaths on or before asOf
phase1Spare = N − Σ ALL Phase 1 deaths − Σ ALL live pots
potAlive(pot, asOf) = pot.plant_count − Σ deaths logged against that pot up to asOf
phase2Alive(asOf) = Σ potAlive over pots potted on or before asOf
totalAlive = N − Σ every death (Phase 1 and pot deaths) — survival, unchanged
pottedShare = Σ live pot.plant_count / N — informational
A potting dated D counts as already happened for Phase 1 check-ins dated D.
phase1Cohort is the denominator of the stage percentages and warnings (for an
experiment without pottings it is simply N, so old data reads exactly as before);
phase1Remaining is the "plants still in propagation" count.
Why phase1Spare and why the limit is the latest point in time. Phase 1 only
ever shrinks. The number of plants that can still leave it (by a potting or a
Phase 1 death) is therefore set by the end of the timeline, not by the date of the
entry being added: if 10 plants had 3 deaths on Jan 20 and a potting of 5 on Jan
15, a new potting dated Jan 2 can take at most 2 — taking more would leave the
later death and potting without plants, even though phase1Remaining(Jan 2) is
10. So "plants available to pot", the cap on new / edited Phase 1 deaths and the
create_potting database function all use phase1Spare (it is the same rule on
both sides). Pot deaths are capped at potAlive; health counts above potAlive
only warn.
phase (propagation / potted), pot_label and
health_good, health_ok, health_poor columns. When the experiment (or any
experiment in the folder) has pots, a second file …-pots.csv is downloaded
right after it: one row per pot with potting date, label, plants in pot, plants
alive, size, substrate, potting and pot notes, and photo URL.Each folder can carry a recurring reminder — a task like "Change water" every few days. The folder shows whether it's due or overdue with a Mark done button, and folders needing attention are badged in the folder list.
Comparing experiments is the point of the app, so a folder with more than one experiment also shows:
Other tabs:
The layout adapts to the screen: a side navigation rail on desktop, a bottom bar on phones.
The web app is live at https://plant-experiments-app.vercel.app (deployed
from main via Vercel) and also runs locally with npm run dev.
It's an installable PWA — open the site on a phone or desktop and use the browser's "Install app" / "Add to Home Screen", and it runs in its own window with the plant-green icon. The service worker precaches the (content-hashed) build so it launches fast and picks up each new deploy silently; nothing from Supabase is cached, so data is always live. There is no offline mode — the app needs a connection to load its data.
The Android app is a thin Capacitor shell that now loads the live site
directly (server.url in capacitor.config.ts) instead of a copy baked into
the APK. So a git push — which redeploys Vercel — updates the phone on its
next launch, no USB reinstall. You only rebuild and sideload the APK
(npm run apk:install) when native code changes: a plugin, capacitor.config.ts,
or AndroidManifest.xml. The shell keeps the native pieces the PWA can't do —
the 11:00 daily care-reminder notifications, hardware back button, themed status
bar/splash, and native token storage. The service worker is not registered
inside the shell, so the WebView always shows the freshest deploy. The phone
build is deliberately slimmer than the web app — it drops the PDF/CSV export
buttons and the top-of-experiment photo shortcut (no file downloads on a phone),
its bottom-bar labels are shortened (Exp. / Feeding / Pests), and every photo
field — log entries and the folder / experiment cover image on both the create
and edit forms — offers an explicit Take photo vs Choose from device
choice. The auth token is stored through @capacitor/preferences (native
SharedPreferences) rather than the WebView's localStorage, which the OS was
evicting on cold start and logging the user out — you now stay signed in until
you explicitly sign out. When the soft keyboard opens, Android shrinks the app window
(android:windowSoftInputMode="adjustResize" on the activity) so the layout
shrinks with it and the focused field scrolls into view.
Two traps live behind that one line, both of which produce the same symptom — a dead band between the form and the keyboard — so they are worth writing down.
@capacitor/keyboard's resize option does nothing on Android. The plugin
never calls setSoftInputMode and never reads that key (setResizeMode is
unimplemented()); it is iOS-only. Without the manifest attribute the window
defaults to adjustPan, the web view keeps its full screen height, and the
unused space inside <main> shows up as the dead band.
The page must not ask for viewport-fit=cover. Capacitor's built-in
SystemBars plugin pads the web view's parent by the keyboard inset when the
page requests cover and the system web view is >= 140. That is the right thing
on Android 15+, where the window does not resize — but on Android 14 and below
adjustResize has already shrunk the window, so the inset is subtracted twice
and the web view collapses to a sliver of its parent. SystemBars still handles
insets on Android 15+ without the meta tag (that branch is gated on the SDK
level, not on the tag), so dropping it costs nothing.
Both are diagnosable over adb rather than by guessing:
adb shell dumpsys window windows | grep sim=
reports the window's real soft-input mode (adjust=resize vs adjust=pan), and
adb shell uiautomator dump /sdcard/ui.xml shows whether the WebView bounds
match its parent ViewGroup — if the web view is shorter, something is
subtracting the inset twice.
Working today:
/experiments/:id/pot: a date, optional notes and a
list of pots (label, plants, size, substrate, notes, photo — camera or gallery),
with Add another pot / Remove pot / Save potting. "Plants available
to pot" is phase1Spare (not the Phase 1 count on the chosen date, so a
backdated potting leaves room for later pottings and deaths); the save is
blocked above it, and the "up to X may be ready (R2: a, S3: b)" hint is display
only. Photos upload first, then runs once; if it fails the
message is shown, the form stays and the uploaded photos are deleted. The pot
list is its own controlled component,
(, , , , , ),
with / in , so the
folder-level wizard can reuse it. A section on the experiment page
(only once a potting exists) lists each potting and its pots (alive / in pot,
size, substrate) with (: label,
size, substrate, notes, photo, plants — never below the deaths already logged
for the pot, never above its count plus the plants left in Phase 1) and
(confirms how many plants return to Phase 1 and how many pot
check-ins go to the bin with it).Photos are automatically shrunk before upload so saving stays fast.
Database changes live in db/ as dated SQL files. They are idempotent and
transaction-wrapped, and you run them yourself in the Supabase SQL editor — the
app expects the columns they add, so a new one has to be run before the features
in that release will work. The newest is db/2026-10-17_phase2_potting.sql:
the pottings and pots tables (soft-delete columns, RLS on the owning
experiment), date_logs.phase (propagation | potted, existing rows backfill
to propagation), date_logs.pot_id with a CHECK that Phase 2 rows always have a
pot, date_logs.health_good|ok|poor, and the create_potting function (one
transaction per potting, security invoker, capped at phase1Spare). It has
already been applied to the live project; run it on a fresh Supabase project
before using Phase 2 there.
capture="environment"). Swapping in the native @capacitor/camera plugin
would add an in-app preview/retake step and is the main remaining Android
polish item.ANDROID_BUILD.md.npm install
npm run dev
Configuration (Supabase URL and key) lives in a git-ignored .env.local file.
Build the production web bundle with npm run build. For the Android app,
npm run apk builds a debug APK and npm run apk:install pushes it to a
USB-connected phone — see ANDROID_BUILD.md for the one-time toolchain setup.
npm run apk:install needs USB debugging, which lives under Developer options.
On Samsung (One UI):
adb devices should list the phone as device.The frontend is a static Vite build; Supabase is the backend and is already
hosted. Deployment is configured for Vercel (vercel.json). One-time setup:
At vercel.com/new, sign in with GitHub and import
the plant-experiments-app repository. Vercel reads vercel.json, so the
framework, build command (npm run build) and output (dist) are already
set — don't override them.
Under Environment Variables add, for all environments:
VITE_SUPABASE_URL — same value as in your local .env.localVITE_SUPABASE_ANON_KEY — same value as in your local .env.local
(The anon key is safe in a client bundle; row-level security is what protects
the data.)Click Deploy. You get a *.vercel.app URL; every later push to main
redeploys automatically.
In Supabase → Authentication → URL Configuration, set the Site URL to your Vercel URL and add these to Redirect URLs:
https://<your-app>.vercel.app/reset-passwordhttp://localhost:5173/reset-password (for local dev)The password-reset email link uses these; without them Supabase falls back to the Site URL. (Also needed if you ever turn email confirmation back on — it's currently disabled.)
create_pottingsrc/components/PotRowsEditor.tsxrowsonChangeavailableshowErrorsdisabledfirstNumbervalidatePotRowssavePottingsrc/lib/utils/potting.ts/experiments/:id/pots/:potId/editphase = 'potted') belongs
to exactly one pot. The date-log pages are phase-aware: the phase comes from the
URL — /experiments/:id/logs/new?phase=potted&pot=<potId> opens the pot
check-in form (date, required Pot selector, plants died with a cause once
0 and capped at that pot's
potAlive— the edited log's own deaths added back viaexcludeLogId— health good / ok / poor with a soft warning when the sum exceeds the plants alive after this entry, photo, notes; no root/leaf tracks); withoutphase=pottedit is the unchanged Phase 1 form. Editing a log (/experiments/:id/logs/:logId/edit) follows the log's ownphase./experiments/:id/logs/photo?pot=<potId>is the Phase 2 quick photo. Each pot in the Pottings section has Add check-in, Log photo (web only, like the experiment's own), Plant watered and Fertilized (one tap, dated today); all writephase='potted'+ thatpot_id(a DB CHECK enforces the pot). The timeline shows a phase badge (once the experiment has a potting), "Pot: ", "Health g / o / p" and the death chip on pot check-ins, and each potting as a row ("Potted 4 plants into 2 pots"). Pot deaths count in the experiment's survival (deaths_countas before). The care schedule is unchanged. Helpers:src/lib/utils/potLogs.ts.
phase1Spare ·
phase2Alive · every death, so the three add up to the plant count) and the
page splits into two tabs, Phase 1 — Propagation and Phase 2 — Potted
(the second only exists with a potting; the last tab per experiment is kept in
component state only). Phase 1 holds the latest-check-in snapshot, the stage
charts and the timeline filtered to Phase 1 (potting rows stay in it). Phase 2
holds an alive-in-pots / potted-share line, the Health in pots chart, the
pot list (PottingsSection: photo, label, alive / in pot, size, substrate, last
check-in and the per-pot buttons, plus Delete potting) and a pot timeline
with All pots / filter buttons. StageChart takes the Phase 1 logs
(stage counts are already the plants still in Phase 1, so the bars shrink after
a potting) and draws a dashed line + "Potted " label in the gap before the
first check-in on or after each potting date. PotHealthChart (inline SVG, from
phase2Trend in src/lib/utils/phase2Trend.ts) stacks good / ok / poor per
Phase 2 check-in date — each pot's latest health count up to that date — with
an "Alive in pots" line. DateLogTimeline got phase, potId and emptyText
props for the filtering.pottings
(a potting event on a date) and pots (individual pots, each with its own
plant count and photo), plus phase / pot_id / health_good|ok|poor on
check-ins and an atomic create_potting RPC that refuses to pot more plants
than Phase 1 still holds. Pottings and pots already go through the bin
(delete/restore/purge, pot photos included) and are included in the JSON
backup. See PHASE2_PLAN.md for the plan./folders/:folderId/pot?ids=… (PotSelectedPage.tsx). One shared date plus optional shared pot size,
substrate and notes, which are copied into every pot row as you type and can
still be edited per pot (rows added later start with them too). Each selected
experiment gets its own section with the plants available to pot, the "up to X
may be ready" hint and the same pot-row editor as the single-experiment flow.
Save uploads photos and calls create_potting once per experiment, one after the
other (each its own transaction). If some fail (for example someone logged
deaths meanwhile and the RPC refuses to over-pot), the page lists the ones
already potted, drops them from the form and keeps the failed ones, each with
the server's message, for Retry failed pottings. Folder cards, the folder
page (a "Phases" tile and every experiment row) show "A propagating · B
potted": A = plants still in Phase 1 (phase1Spare), B = plants alive in pots
(phase2Alive); helpers in src/lib/utils/folderPhases.ts. The folder
comparison's snapshot table has two informational columns, Potted % of
original and Alive in pots; the winner callout, ranking and status are
untouched (decision 8).src/lib/utils/phases.ts: phase1Cohort(asOf) (plants still part of Phase 1
on a date — the denominator of stage percentages and warnings, which for an
experiment without pottings is simply the plant count), phase1Remaining(asOf)
(cohort minus Phase 1 deaths), phase1Spare() (plants that can still leave
Phase 1 at all — the cap for "plants available to pot" and for Phase 1 deaths,
so a backdated entry always leaves room for later ones), potAlive,
phase2Alive, totalAlive, pottedShare and the "up to X may be ready"
hint readyToPot (min of R2 and S3). Stage snapshots, comparison tables,
warnings and the log form's death cap use them; phase 2 (potted) check-ins
are ignored by the root/leaf stage views. Survival keeps its formula (plant
count minus every death), and the folder summary now also carries the potted
share and plants alive in pots (informational, not used for the winner).