| Решение | Причина |
|---|---|
| Go/Gin + HTML templates, не SPA | SEO, быстрый first paint, работает без JS. Preact только для progressive enhancement |
| Preact, не React/Vue/Angular | Минимальный бандл (~3KB). Пользователи часто на медленном интернете |
| DaisyUI + кастомные токены (w-*) | Единая дизайн-система, быстрая разработка. Свои токены поверх DaisyUI night theme |
data-async progressive enhancement |
Формы работают без JS, async-атрибуты добавляют AJAX поведение поверх |
| Webpack 5, не Vite | Стабильность, проверенные плагины, нет причин мигрировать |
| Модели в models/, не в handlers | Чёткое разделение DB-логики. Handlers никогда не делают SQL-запросы напрямую |
| Two-level handler pattern | HTTP-слой отделён от бизнес-логики. Упрощает тестирование и переиспользование |
| 4K стриминг отключён | Транскодирование 4K слишком ресурсозатратно, стриминг больших файлов нестабилен |
| go-i18n + path prefix routing | SEO (каждый язык — отдельный URL), обратная совместимость (EN без префикса), HTTP middleware до Gin routing |
withContext для sub-templates |
Lang не протекает в domain structs. Контекст передаётся отдельно через wrapper |
| Translation keys в Go коде | Tool titles, sort labels, button names, job messages — ключи вместо строк, перевод в шаблоне/runtime |
- Full guide:
docs/i18n.md— архитектура, паттерны, правила переводов - Languages: EN (default, no prefix), RU (
/ru/), ES (/es/), DE (/de/), FR (/fr/), PT-BR (/pt/), IT (/it/), PL (/pl/), TR (/tr/), NL (/nl/), CS (/cs/) - Locale files:
locales/{en,ru,es,de,fr,pt,it,pl,tr,nl,cs}.json— flat key-value JSON - PT note: bundle
ptсодержит PT-BR.Accept-Language: pt-BR/pt-PTоба сворачиваются вptчерезlanguage.Base(). Разделять наpt-br/pt-pt— только когда потребуется европейский португальский (нужна правка middleware для переменных префиксов) - Template translation:
{{ t $.Lang "key" }},{{ tp $.Lang "key" "Param" value }} - Links: всегда
{{ langPath $.Lang "/path" }} - Sub-templates с чужими данными:
{{ template "name" withContext $ data }} - Job messages:
i18n.Serviceпробрасывается черезJobs→ скрипты,s.t("key") - Не переводим: brand names (Vault, Discover, Stremio), technical terms, legal pages
- Русский: "Смотрите" вместо "Стримьте", "Вклад" вместо "Залог"
- Before starting work, check the
docs/directory — it contains technical specs, DB schemas, business logic, API specs, and edge cases for major features (e.g.,docs/vault.md). - After completing any task, update the relevant docs — document new methods, services, DB tables, and changed functionality. Documentation updates are mandatory.
- Go 1.26 (module:
github.com/webtor-io/web-ui) - Node 22.x for frontend assets
- npm (not Yarn) —
package-lock.jsonis present - Frontend assets: webpack →
assets/dist, served at/assets - Public static files:
pub/mounted at/and/pub
make build— runsnpm run buildthengo build .make run— runs./web-ui s(serve)npm start— hot reload viaair(Go) + webpack-dev-server (requiresair:go install github.com/cosmtrek/air@latest)go test ./...— run all Go testsgo test ./services/parse_torrent_name -v— parser tests (golden-file based)go test ./services/parse_torrent_name -run TestParser -update— update golden files
docker build .
Produces minimal 3-stage Alpine image. Exposes ports 8080, 8081. Entrypoint ./server serve with GIN_MODE=release.
- ALL database operations go in
models/— handlers must never contain direct DB queries. - Model files named after the entity (e.g.,
models/embed_domain.go). - Model methods accept
*pg.DBas first parameter. - Provide Get/List, Create, Update, Delete, Count/Exists methods per entity.
All handlers must follow two-level separation:
- Level 1 (HTTP layer): Extracts params from
gin.Context, calls Level 2, handles HTTP responses. - Level 2 (Business logic): Pure functions, no
gin.Contextdependency, returns values and errors. - Auth: Use
auth.HasAuthmiddleware viar.Group().Use(auth.HasAuth)— don't check auth manually in handlers. - Reference:
handlers/embed_domain/handler.go,handlers/vault/handler.go,handlers/streaming/backends/handler.go
- Server-side rendering first — use Go templates with Gin, minimize client-side JavaScript.
- No heavy JS frameworks (React, Vue, Angular).
- Use HTML forms with
method="post"for mutations. - Use
data-asyncattributes for progressive enhancement. - Use
data-async-targetanddata-async-push-state="false"for partial page updates. - Handlers use
c.Redirect(http.StatusFound, c.GetHeader("X-Return-Url"))for form processing. - Templates in
templates/partials/, registered via TemplateManager beforetm.Init(). - Good examples: Stremio (
templates/partials/profile/stremio.html), WebDAV, Embed domains.
- Container needs unique
idanddata-async-layoutattribute. - Hidden form with
data-async-targetpointing to containeridanddata-async-push-state="false". - Use
requestSubmit()instead ofsubmit(). - Call
document.querySelector("#component-id").reload()for direct reload. - Reference:
templates/partials/vault/button.html,templates/partials/library/button.html
- Clipboard functionality
- Progressive enhancement of server-side features
- Interactive features that don't break core functionality without JS
- Analytics (Umami)
go fmtandgo vet- Logging: global
logrus(log "github.com/sirupsen/logrus"), not injected loggers - Structured logging with
WithField(),WithError()— messages start with lowercase - Error wrapping:
github.com/pkg/errorswitherrors.Wrap(err, "context") - Log errors only at the top level (handlers/entry points) — lower levels wrap and return
- External calls: use
context.WithTimeoutandlazymapcaches - Interface names: no "Interface" suffix (e.g.,
StreamServicenotStreamServiceInterface) - Implementation structs: descriptive names (e.g.,
HttpStreamService)
- File naming:
{number}_{description}.{up|down}.sql(e.g.,19_create_stremio_settings.up.sql) - Use
public.schema prefix, tab indentation, lowercase data types - Constraint naming:
{table}_pk,{table}_{column}_unique,{table}_{reference}_fk - Include
update_updated_attrigger for tables withupdated_at - Down migrations:
DROP TABLE IF EXISTS table_name; - Follow patterns from
18_addon_url.up.sql
- Tailwind v4, webpack 5, postcss
- Stylelint available (not wired to npm scripts)
- UIKit reference:
docs/uikit.html— open in browser afternpm run buildto see all design tokens and components
The project uses a custom design system on top of DaisyUI (night theme). All tokens and components are documented in docs/uikit.html. When adding or changing UI components, consult and update docs/uikit.html to keep it in sync.
Color tokens (tailwind.config.js → w-*): bg, surface, card, pink, pinkL, purple, purpleL, cyan, text, sub, muted, line. Use as bg-w-{name}, text-w-{name}, border-w-{name}.
Button variants — each has a designated context, do NOT mix:
| Variant | Use for | Do NOT use for |
|---|---|---|
btn-pink |
Homepage & tools page CTAs | Profile, vault, auth, support form |
btn-soft |
Profile, auth & support form actions | Homepage CTAs, vault/library |
btn-soft-cyan |
Secondary actions in cyan (info, tools, content) | Primary actions, profile/auth |
btn-accent |
Vault, library actions | Homepage, profile, auth forms |
btn-ghost border border-w-line |
Outlined ghost (nav, downloads, secondary) | Primary actions |
btn-ghost (no border) |
Tertiary actions (demo, delete, logout) | Primary actions |
Focus color — context-dependent:
focus:border-w-pink— profile & auth page inputsfocus:border-w-cyan— support form & tools page inputs
Badge color — matches section theme:
- Pink (
bg-w-pink/10 text-w-pinkL) — features, profile - Purple (
bg-w-purple/10 text-w-purpleL) — comparison sections - Cyan (
bg-w-cyan/10 text-w-cyan) — info, tools, FAQ
Custom CSS classes (assets/src/styles/style.css): btn-pink, btn-soft, btn-soft-cyan, toggle-soft, gradient-text, gradient-stat, hero-glow, cta-glow, upload-dashed, navbar-redesign, collapse-webtor, progress-alert, promo, promo-close, promo-compact, loading-elipsis, popin, w-card-frame, w-card-title, w-card-badge, w-card-badge-label, w-card-badge-ghost, w-card-stars-compact, w-card-stars-full.
Mobile patterns — see docs/uikit.html section 15. Key rules:
- Hide decorative badges/subtitles on mobile (
hidden sm:inline-flex/hidden sm:block) - Use
sticky top-[72px]for tab bars withbg-w-bg/90 backdrop-blur-lg - Use
flex-col sm:flex-rowto stack filters below tabs on mobile - Use container queries (not media queries) for card-level responsive:
w-card-badge-label,w-card-stars-compact/fullswitch at 210px card width - Use
line-clamp-2instead oftruncatefor torrent names - Touch targets:
w-card-badge-ghostenlarges to 2.5rem on@media (hover: none)
Typography (tailwind.config.js):
font-sans— Inter (primary body font)font-logo— Comfortaa (logo/branding)- Font CSS embedded as base64 WOFF2 in
assets/src/styles/inter.cssandcomfortaa.css
WEB_HOST/WEB_PORT(default 8080)- REST API:
REST_API_SERVICE_HOST,REST_API_SERVICE_PORT, or RapidAPI viaRAPIDAPI_HOST/RAPIDAPI_KEY - Sessions:
SESSION_SECRET(optional Redis viaREDIS_*vars) - Assets:
ASSETS_PATH(default./assets/dist) - DB: PostgreSQL via
common-servicesflags (PG_HOST, etc.) — migrations auto-apply on startup - Redis: for job queues via
common-services
- Umami analytics:
USE_UMAMI,UMAMI_WEBSITE_ID,UMAMI_HOST_URL - GeoIP:
USE_GEOIP_API,GEOIP_API_SERVICE_HOST/PORT - Claims (user tiers):
USE_CLAIMS,CLAIMS_PROVIDER_SERVICE_HOST/PORT - Stremio addon:
STREMIO_ADDON_USER_AGENT,STREMIO_ADDON_PROXY
- After committing and pushing to git, always suggest running
/deployto deploy the changes.
- pprof/probe via
common-servicesflags (secondary port) - Test API without RapidAPI: port-forward
rest-apifrom K8s or setREST_API_SERVICE_HOST/PORT - Asset path issues: use
--assets-pathorWEB_ASSETS_HOSTfor CDN - Ad testing: set cookie
test-adsor query paramtest-ads