Sonax Care
cob 커널로 만든 Flutter + Serverpod 워크스페이스다. 기능은 feature(모듈)로 더하고, 모듈은 정해진 자리(슬롯)에만
꽂힌다. 첫 줄의 앱 이름은 정체성 값이라 identity.yaml 이 관리한다.
Features
- care — usecases: get_care_records
구성
app/app 앱 셸 — 부트스트랩 · DI · 라우터 조립 (android · ios · macos · web · windows)
app/console 콘솔 셸 — 별도 DI · 라우터 조립
app/app_widgetbook Widgetbook 조립 앱(sink)
feature/app_router/app 앱 라우터 번들 — 최상위 라우트 목록 + 상태 화면(@TypedGoRoute)
feature/console_router/console 콘솔 라우터 번들 — 별도 라우트 목록
feature/<domain>/{app,console,server,client,widgetbook} 기능 소유 역할 패키지
package/core 모듈 공용 계약 — 아래 「모듈 계약」
package/resources 테마 — coui 프리셋 위의 AppTheme
package/serverpod_service serverpod 클라이언트 접근 (ServerpodService.instance)
package/test_driver feature 테스트 도구 — buildFeatureTestApp · 골든 매트릭스(golden.dart)
shared/env_config 정체성(AppIdentity) · flavor · 환경변수(.env.<flavor>, envied) · 서버 주소
shared/dependencies 서드파티 배럴 — bloc · Either · go_router · coui · hooks · signals …
shared/i10n 번역 (slang · 11개 로케일 — ko en zh-Hans ja de fr es pt it ru ar)
backend/sonax_care_server Serverpod 서버 (인증 포함 · 내장 Postgres) — 이름은 identity.yaml project_name
backend/sonax_care_client serverpod_cli generate 가 만드는 클라이언트
tool/gates 계약 게이트 — 아래 「게이트」
tool/serverpod serverpod 버전 · 프로필의 유일한 출처(serverpod.yaml · profile.py)
tool/bdd BDD 생성(co_bdd — .feature → widget 테스트)
tool/golden 골든 캡처 · 비교 · 갤러리(co_golden · co_golden_gallery)
tool/dev 로컬 개발 도구 — write_env · write_passwords · test_all
tool/feature_modules 번들 검사 · 모듈 DAG 생성 · 역할 테스트 · 생성물 목록
tool/i18n 번역 패리티 검사(11개 로케일 · 키 · 자리표시 · 줄바꿈 · ko 밖 한글)
tool/web 웹 첫 프레임 전 스크립트(pre-boot) 원천 · 판정 표
tool/fonts 다국어 폴백 글꼴(LocaleFallback — Noto 부분집합) 만들기
tool/pages Pages 사이트(랜딩 · surfaces) · 문서 렌더(docs) · 공개 스모크(smoke)
dependency_layers.json 패키지 계층 · 런타임 경계 래칫(0 기준선)
.cob/feature-layout.json 신규 bundle 기본값(기존 프로젝트 marker 는 보존)
identity.yaml 정체성 값과, 그 값이 있어도 되는 파일 목록
slots.yaml 모듈이 꽂히는 자리(마커) 목록
빠른 시작
fvm install && fvm use # Flutter 3.47.6 (.fvmrc)
flutter pub get # coui · co-package(비공개 coco-de) 를 읽을 GitHub 권한이 필요하다
tool/dev/write_env.sh # 앱 환경변수 .env.<flavor> + envied 생성 — 커밋하지 않는다
tool/dev/write_passwords.sh # 서버 비밀번호 파일 — 로컬 전용, 커밋하지 않는다
# 서버: 내장 Postgres 가 함께 뜬다 (Docker 불필요)
(cd backend/sonax_care_server && dart run bin/main.dart --apply-migrations)
# 앱: 개발 flavor 는 http://localhost:8080/ 에 붙는다
(cd app/app && flutter run -d chrome)
tool/serverpod/cli.sh start 를 쓰면 서버와 앱이 함께 뜬다(서버 pubspec 의 serverpod: flutter_apps:).
serverpod CLI 는 전역 설치가 아니라 tool/serverpod/cli.sh 가 고정 버전(tool/serverpod/serverpod.yaml)으로
돈다 — serverpod_cli 는 analyzer <11 을 요구해 워크스페이스에 넣으면 모든 코드젠이 옛 analyzer 에 묶이므로
별도 pub 루트(tool/serverpod/cli)에 둔다. 실서비스 런타임의 첫 화면은 고객 예약 또는 점주 로그인이다. localOnly 진단 경로는 커널 회귀 테스트에 사용한다.
서버 이미지는 저장소 루트에서 docker build -f backend/sonax_care_server/Dockerfile . 로 만든다.
Docker 빌드가 workspace 경로를 보존한 서버 runtime 의존 폐포를 만들고 Flutter 역할은 제외한다.
.dockerignore 가 로컬 passwords.yaml · envied · .serverpod 를 빼므로 이미지에 들어가지 않는다 —
운영 비밀은 SERVERPOD_PASSWORD_<키> 환경변수로 넣는다. 키 목록은
shared/env_config/KEYS.md.
빌드 정의 (--dart-define)
| 이름 | 값 | 미지정 시 |
|---|---|---|
FLAVOR |
development · staging · production |
development |
API_URL |
서버 절대 URL — 모든 flavor 에서 가장 앞선다 | — |
DEV_SERVER_BASE_URL |
개발 서버 주소 — cob dev · serverpod start 가 넣는다 |
http://localhost:8080/ |
COB_RUNTIME_MODE |
production · localOnly(서버 없는 데모 — DemoWorld) |
production |
COB_DEMO_ONLY |
true — 데모 전용 앱. localOnly 가 아니면 "서버 없음" 으로 끝낸다 |
false |
WEB_URL_STRATEGY |
path(실서비스 · 유니버설 링크) · hash(GitHub Pages — SPA 폴백 없음) |
path |
서버 주소는 --dart-define=API_URL → .env.<flavor> 의 API_URL → (development 만)
DEV_SERVER_BASE_URL → localhost 순이다(EnvConfig). 운영·스테이징에서 둘 다 없으면 원격 연결을 만들지 않는다. 계약 밖의 값은 기본값으로 떨어뜨리지 않고
진단 화면으로 끝낸다 — 오타 난 운영 빌드가 개발 설정으로 도는 사고를 막기 위해서다.
모듈 계약
cob compose 가 만드는 feature 와 모듈은 이 워크스페이스가 다음을 준다고 가정하고 쓰인다.
| 모듈이 쓰는 것 | 주는 곳 |
|---|---|
package:core/core.dart — Failure 계열 · UseCase · NoParams · Log · ServiceLocator · RuntimeConfig · context.i10n |
package/core |
위와 함께 오는 서드파티 — bloc · Either · equatable · go_router · hooks · intl · coui |
shared/dependencies (core 가 다시 내보낸다) |
생성된 서버 모델을 접두사 없이 — compose 는 동명 엔티티를 hide <Entity> 로 가린다 |
core 가 package:serverpod_service 를 다시 내보낸다 |
ServerpodService.instance.client · ServerpodClient |
package/serverpod_service |
bloc_signals · bloc_signals_flutter (선언 없이 import) |
core 가 워크스페이스에 들여온다 |
buildFeatureTestApp (테스트) · kernelGoldenMatrix (package:test_driver/golden.dart) |
package/test_driver |
화면 · 위젯 — HookWidget + signals_hooks(useSignal · useFutureSignal …). StatefulWidget 금지 |
shared/dependencies |
셸의 언어 · 테마 상태 AppPresentation(URL 첫 값 · 미리보기 실시간 · 저장 없음) · DemoPrefs · DemoLocale |
package/core (co_demo_prefs 를 다시 내보낸다) |
데모 표시 데이터 투영 — DisplayProjector · DisplayKey · DisplayGenerator · DemoFormat · CoFaker |
package/core (co_demo_world · co_faker 를 다시 내보낸다 — 모듈은 co-package 를 따로 선언하지 않는다) |
라우트 — @TypedGoRoute + <Name>RouteName.base 를 AppRoutes.routes 마커에 |
feature/app_router/app |
콘솔 내비게이션 — ConsoleNavigationItem(typed route · 이름/번역 키 · 아이콘 · 순서 · 권한), 라우트/메타데이터 슬롯에 등록 |
package/core · feature/console_router/console — 등록 계약 |
| 서버 호스트 계약 · 인증 조립 | backend/sonax_care_server — 기능 모듈은 호스트를 import 하지 않는다 |
UI 는 coui 로 쓴다 — core 를 import 한 파일에서 package:flutter/material.dart 를 함께 쓰면
Scaffold · Theme · ThemeData 가 겹친다.
feature 가 따를 규약
| 무엇 | 어디 · 어떻게 |
|---|---|
| 파일 | 한 파일 = 공개 타입 하나, 파일 이름 = 타입 이름 snake_case(get_post_use_case.dart 에 GetPostUseCase). 예외 규칙은 check_one_class_per_file.py 머리 주석 |
| 화면 | HookWidget + signals_hooks. 불가피한 StatefulWidget 은 tool/gates/stateful_widget_allowlist.txt 에 사유와 함께 |
| 레이아웃(스케일 불변) | 고정 픽셀 높이 예산을 합산하지 않는다 — 콘텐츠 주도(스크롤 · minHeight 하한). 화면 위젯 테스트는 testScaleInvariant(package:test_driver)로 coui scaling 1.25 × textScaler 1.0 · 1.15 · 1.3 · 2.0 4조합(SSOT — kDefaultScaleInvariantScaling · kDefaultScaleInvariantTextScalers)을 돌고(조합 · 판정 코어는 co_bdd — test_driver 는 커널 기본 배율을 채우고 같은 선언을 재수출한다), buildScaleInvariantApp 으로 띄워 expectNoLayoutOverflow · expectNoTextClipping(예외 없이 잘리는 텍스트)을 단정한다. 방향은 surfaceSize(세로 · 가로 — 파생식은 주석). 매트릭스를 화면마다 다시 짜지 않는다. 예: feature/app_router/app/test/src/app_status_scale_invariant_test.dart |
| 라우트 | lib/src/route/<name>_route.dart 에 @TypedGoRoute(part .g.dart), <Name>RouteName.base 가 생성 게터를 돌려준다. 경로는 웹 URL · 유니버설 링크와 같다. dev_dependencies: build_runner · go_router_builder |
| BDD | test/src/bdd/<name>.feature · test/src/bdd/step/<step>.dart, build.yaml 의 co_bdd|dual_test_gen(sharedSteps · widget-only), dev_dependency co_bdd(co-package git — 다른 co-package 의존과 같은 ref, check_git_dep_refs.py). 생성물 *.widget_test.dart 커밋. tool/bdd/generate.sh [--check] [패키지 경로 …] — 경로를 주면 그 멤버만 만들고 --check 도 그 경로만 본다. co_bdd 멤버가 아닌 경로 · 모르는 옵션은 생성 없이 종료 코드 2 |
| 골든 | test/golden/<화면>_golden_test.dart 에 kernelGoldenMatrix(suite: '<패키지>').scenario(...), 기준 test/golden/goldens/**(CI 셀프호스트 macOS 러너가 만든다), test/flutter_test_config.dart 는 export 'package:test_driver/golden.dart' show testExecutable;, dart_test.yaml 에 tags golden · co_golden |
| 시계 | 화면 · 유즈케이스는 AppClock.now()(core). 골든이 기준 시각으로 고정한다 |
| 데모(localOnly) | 모듈의 localOnly 저장소는 DemoWorld(core)를 읽고 쓰고, 시드는 register_demo_world.dart 의 demo-seed 마커에. 변경 신호는 쓰기에만, 시드 이력은 isSeed 항목에만 |
| 데모 표시 데이터(언어별) | DemoWorld 에는 업무 데이터(A)만(시드는 world.config.businessFaker(key: '<컬렉션>')). 가상 인물 · 상품 이름 같은 표시 데이터(B)는 기능이 <Name>DisplayFields.fields(entity.field → 생성기)로 내보내 demo-display(앱) · console-demo-display · widgetbook-demo-display 마커에 꽂고, 화면은 DisplayProjector.text(DisplayKey(...)) 로 읽는다 — 언어를 바꾸면 그것만 다시 투영된다(목데이터 다국어 #1065). 예: feature/app_router 상태 화면의 표본. seed · 기준 시각은 DemoWorld.projectConfig 한 곳 |
| 언어 · 테마(S-18) | 화면은 context.i10n · 기능 번역만 읽는다 — 첫 값 · 실시간 · RTL 은 셸(AppPresentation)이 한다. 라우트 · 입력 · 선택은 언어와 다른 상태에 둔다(전환이 건드리지 않는다). 날짜 · 금액은 DemoFormat(한국어 패턴을 코드에 두지 않는다) |
| Pages 산출물 | tool/pages/surfaces/<id>.sh OUT BASE 하나 + tool/pages/catalog.tsv 한 줄 |
| 환경변수 | 새 키는 shared/env_config/.env.example · Env<Flavor> · FlavorEnv · KEYS.md 에 함께 |
| 번역 | feature/<domain>/{app,console}/lib/src/i18n/ — 기능 소유 slang 카탈로그 + featureTranslations(context, feature_i18n.AppLocaleUtils.instance). 11개 로케일 모두(tool/i18n/check_parity.py --strict 가 게이트) |
| 재사용 | 새 위젯 · 도구를 짜기 전에 사내 패키지(coco-de/co-package: open_board(그리기 · 주석), co_arc, open_epub …)와 coui 컴포넌트를 먼저 찾는다 |
계약
번들 조립(AD-1). feature/<domain>/bundle.json 은 schemaVersion: 1, feature,
requires(번들 DAG), packages(역할 → 상대 경로), 선택적 hostTests(프로젝트 루트 기준 호스트 테스트
파일 목록)만 담는다. 실제 이름 예외는 host-integration.json 의 packageNames 에 둔다.
기본 이름은 <domain> · console_<domain> · <domain>_server · <domain>_client ·
<domain>_widgetbook 이다. server 는 type: module; 호스트·테이블 소유권은 하나다.
호스트 포트는 서버 인스턴스/세션 인지 계약으로 바인딩하며 미구현 어댑터는 fail-closed 로 둔다.
tool/feature_modules check # 경로 · 이름 · workspace · requires · 호스트 경계
tool/serverpod/cli.sh generate # 모듈(의존 위상 순서) → 호스트
tool/serverpod/cli.sh create-migration # 같은 순서로 모듈 metadata → 호스트 DDL
tool/feature_modules flutter-codegen # 기능 소유 slang + workspace build_runner(중첩 Widgetbook 포함)
tool/feature_modules test --modules-only # Flutter 역할은 flutter test, 서버/클라이언트는 dart test
tool/feature_modules test-unit --modules-only # feature 서버 단위 테스트
tool/feature_modules test-host # passwords 준비 → bundle.hostTests 를 호스트에서 실행
python3 tool/gates/check_package_layers.py # 계층 · 런타임 경계 · 순환 래칫
호스트 테스트(test-host, test/test-unit 의 호스트 단계)는 passwords.yaml 이 없으면
tool/dev/write_passwords.sh 로 준비한다. 기존 파일은 FORCE=1 환경에서도 덮어쓰지 않는다.
준비 실패는 테스트 시작 전에 준비 명령과 함께 오류로 끝난다. 선언된 hostTests 가 없으면 파일을 만들지 않는다.
SERVER_DIR 는 CLI 의 명시적 단일 패키지 어댑터다. 서버 모듈 디렉터리에서 CLI 를 부르는 경우에도 그
모듈만 생성한다. 루트/호스트에서 부르면 전체 DAG 를 실행한다. 누락 의존 · 순환 · 실패는 즉시 멈춘다.
기능 번역(AD-10). 기능의 slang.yaml 은 입력/출력을 lib/src/i18n/ 에 두고
lazy: false, locale_handling: false, flutter_integration: false 를 사용한다. enum_name ·
class_name 은 기능별로 고유하게 정하고 생성 파일을 as feature_i18n 으로 import 한다.
featureTranslations 가 셸 TranslationProvider 의 현재 로케일을 읽고 재빌드에 구독한다.
기능에 없는 로케일은 그 기능의 기본 로케일로 폴백하며 셸 전역 로케일을 바꾸지 않는다.
라우터 app/console 카탈로그와 로케일 전환 위젯 테스트가 실행 가능한 연결 예다.
정체성(Identity-Free). 앱 이름 · 번들 ID · 조직은 identity.yaml 의 files 에만 있다. 코드는
AppIdentity(Dart) · AppInfo.xcconfig(iOS·macOS) · identity.properties(Android)를 참조한다.
슬롯. 모듈은 slots.yaml 에 선언된 마커 아래에만 꽂힌다. 라우트(app_routes.dart) · DI
(register_app_dependencies.dart) · 이 README 의 Features 목록은 cob compose 가 이미 쓰는 자리이고,
나머지(부트스트랩 단계 · 앱 래퍼 · 서버 인증 제공자 · 부팅 훅 · 예약 작업 · 워크스페이스)는 레지스트라보다
자리를 먼저 둔 것이다.
게이트. tool/gates/check_all.sh — CI 와 같은 판정을 로컬에서 낸다.
| 게이트 | 막는 것 |
|---|---|
check_identity.py |
정체성 값이 지정 파일 밖에 나타나는 것 · 값이 빠진 지정 파일 · 중립 서버 이름(backend_*) · project_name 카멜 표기 |
check_slots.py |
마커가 없거나 두 번 있는 것, slots.yaml 에 없는 마커 |
feature_modules.py check |
번들 선언 · workspace 누락 · 이름/역할 불일치 · requires 순환 · 모듈→호스트 · 테이블 중복 소유 |
check_package_layers.py |
미분류 패키지 · 상향 의존 · UI/client→server · 운영→Widgetbook/호스트 · 신규 SCC 멤버 |
check_one_class_per_file.py |
한 파일에 공개 타입 둘 이상 · 파일 이름 ≠ 타입 이름 |
check_no_stateful_widget.py |
StatefulWidget · State(허용 목록 + 사유 밖) |
check_env_files.py |
실값 .env 추적 · envied 생성물 추적 · .env.example / KEYS.md 에 없는 키 |
check_serverpod_version.py |
serverpod 버전이 tool/serverpod/serverpod.yaml 과 어긋난 것(실패) · 최신보다 뒤처진 것(경고) |
check_web_sqlite_assets.py |
offline-sync 프로필의 웹 로컬 DB 자산 누락 · 불일치 |
check_app_links.py |
앱 링크 도메인 배선(iOS · Android · 웹 .well-known)이 identity.yaml 과 어긋난 것 |
check_coui_pin.py |
coui 고정이 원격 최신보다 5개 이상 뒤처진 것(경고) |
check_one_public_class.py |
한 파일 한 공개 타입 — skills(flutter-app-baseline)와 같은 판정 |
check_git_dep_refs.py |
한 pub 워크스페이스 안에서 같은 git 의존(url · path)의 ref 가 갈린 것 · co-package 형제(co_faker · co_demo_prefs · co_demo_world · co_bdd · co_golden · co_golden_gallery)가 저장소 전체에서 한 커밋이 아닌 것(--same-repo-ref) |
tool/i18n/check_parity.py --strict |
슬랭 카탈로그(셸 · 기능)마다 11개 로케일 파일 · 키 트리 · 자리표시 · 링크 · 줄바꿈 · ko 밖 한글 · 빈 값 · 모양 |
check_locale_fallback_font.py |
번역 · co_faker 글자를 번들 글꼴(Pretendard ∪ LocaleFallback)이 덮지 못하는 것 · 글꼴이 기록과 다른 것 · co_faker ref 가 글꼴을 만든 ref 와 다른 것 · 웹 호스트가 LocaleFallback 을 엔진 전역 폴백(Roboto)으로 선언하지 않은 것 |
check_workflows.py |
워크플로 YAML 이 잡을 못 만드는 것(actionlint · 한 줄 run: 의 : ) |
serverpod 버전 · 프로필. 버전은 tool/serverpod/serverpod.yaml 한 곳에서 정한다 —
python3 tool/serverpod/profile.py apply stable --version <x.y.z> --run 이 서버 · 클라이언트 · Flutter ·
인증 · cloud_storage · test · CLI 를 함께 바꾸고 생성 · 마이그레이션까지 한다. 오프라인 동기화가 필요하면
python3 tool/serverpod/profile.py apply offline-sync --run — serverpod 베타 + co-serverpod
serverpod_offline_sync 포크(커밋 고정) + databaseSync 실험 기능 + 웹 로컬 DB 자산(sqlite3.wasm ·
db_worker.js)을 켠다. 켜는 방향만 지원한다(되돌리면 마이그레이션이 모듈 테이블을 지우려 해 멈춘다).
앱 링크. 웹은 경로 URL(usePathUrlStrategy)이라 https://<app_link_domain>/<경로> 가 웹 · iOS
(Associated Domains) · Android(autoVerify) 에서 같은 go_router 경로로 열린다. 도메인은 identity.yaml 의
app_link_domain. 배포 전에 app/app/web/.well-known/ 두 파일의 TEAM_ID · SHA256_CERT_FINGERPRINT 를
채우고, 웹 서버가 모르는 경로를 index.html 로 돌려주게(SPA 폴백) 설정한다.
CI
.github/workflows/ci.yml — 계약 게이트, 생성물 최신 여부(slang · serverpod_cli generate · build_runner/co_bdd 후 작업 트리 불변),
포맷, dart analyze --fatal-infos, 워크스페이스 전 패키지 테스트(tool/dev/test_all.sh · 서버는 내장
Postgres), 웹 빌드, Android debug 빌드(웹·테스트가 컴파일하지 않는 네이티브 층). coui 는 조직 시크릿
SUBMODULE_TOKEN 으로 읽는다. 앱 환경변수는 시크릿 ENV_<FLAVOR>_BASE64(없으면 .env.example).
모듈 → 호스트 codegen 잡이 feature-generated artifact 를 내보내고 검증 레그가 복원한다.
envied 비밀 출력은 artifact 에 포함하지 않는다. feature 서버 단위 테스트 레그는 TZ=UTC 로 돈다.
analyze·generated 레그는 tool/pub_roots.py로 실제 pubspec/workspace 선언에서 독립 pub 루트를 찾아
각 의존성을 준비한다. analyze는 독립 루트의 자체 분석까지 실행한다. 사이트가 없으면 기존 workspace 동작을 유지한다.
.github/workflows/golden.yml — 골든: PR · main push 는 기준 이미지와 비교(골든 (compare), 차이 · 갤러리는
아티팩트), main 밖 브랜치에 커밋 메시지 [update-goldens] 로 push 하거나 dispatch 입력 update_goldens 를 주면
기준 이미지를 CI(셀프호스트 macOS 러너 · Darwin/arm64)가 다시 만들어 그 브랜치에 커밋한다(골든 (update)).
새 프로젝트의 첫 기준선: 커널 이미지의 이름·브랜드·글꼴이 생성 프로젝트와 다르거나 신규 feature 이미지가
없으면, 최초 compare의 golden-failures에서 기준·실제·diff를 검토하고 차이의 이유를 기록한다.
의도한 차이만 확인한 뒤 gh workflow run golden.yml --ref <branch> -f update_goldens=true로 CI 기준을 만들고,
봇 커밋에서 gh workflow run golden.yml --ref <branch> -f evidence=true로 compare를 다시 통과시킨다.
자세한 순서는 CLAUDE.md의 첫 프로젝트 골든 기준선. capture/update를 compare 성공으로 세지 않는다.
main push 와 workflow_dispatch(입력 evidence)에서는 세 워크플로가 변경 범위 판정 없이 전부 돈다 — 데모 증거
대장은 main HEAD 와 같은 SHA 의 run · 잡 이름만 인정한다. 게이트 ↔ 잡 이름 표는 CLAUDE.md 「CI」 절(정본 데이터
tool/ci/evidence_jobs.json).
.github/workflows/pages.yml — GitHub Pages 사이트: PR 은 빌드만, main 은 배포. 빌드 잡이 산출물로
공개 스모크(tool/pages/smoke — Playwright 고정 · lock 커밋)를 한 번 돌리고, 배포 뒤 공개 스모크 잡이 같은
커밋(랜딩의 <meta name="pages-commit">)이 올라올 때까지 기다린 뒤 실제 주소로 다시 돈다 — 7표면 200 · 콘솔 오류 0 ·
허용 출처 밖 요청 0 · 새로고침 · app/?lang=ar&theme=dark 의 첫 프레임 전 상태(lang · dir · color-scheme · 배경 = 앱 스캐폴드 색) ·
iframe 실시간 전환(ko → en → ja → ar → ko · 빠른 연속 — 다시 불러오지 않고 예약 화면 문구가 바뀐다. 고객 입력과 서버 기록은 보존한다). 보고서 JSON 은
아티팩트(pages-smoke). 로컬: cd tool/pages/smoke && npm ci && npx playwright install chromium && node smoke.mjs --base https://<Pages 주소>/<저장소>/ [--local-root ../../../build/pages].
tool/pages/build.sh 가 랜딩(있는 산출물만 카드) · /app/(HTTPS Serverpod 연결 · 해시 URL) ·
tool/pages/surfaces/*.sh 산출물(/site/ · /console/ · /widgetbook/ · /golden/ · /docs/ — 랜딩 포함 7표면)을 만든다. surface 계약은 build.sh 머리 주석,
카드 문구는 tool/pages/catalog.tsv. 비공개 git 의존 토큰은 스텝 env(GIT_CONFIG_*)로만 넣는다. 로컬: tool/golden/capture.sh → tool/golden/gallery.sh
(build/golden-gallery/index.html).
Generated by cob 0.6.0 · bricks: 4e5c2ad6 · base: kernel · 재현: cob generate --base kernel -s project.yaml
소개 사이트와 Linux 앱
site/는 coco-de/cocode-home 구조를 따라 Jaspr static + CoUI Web으로 구성한다.
site/packages/core, site/feature/home, site/shared/i10n_web으로 나뉘며
11개 언어의 소개·서비스·매장 페이지를 생성한다. 한국어는 루트, 다른 언어는 BCP47 접두사다.
모든 플랫폼의 앱 아이콘과 사이트 파비콘은 기본 Cocode 로고를 사용한다.
원본 벡터는 assets/brand/cocode.svg에 있다.
(cd site && bash scripts/build.sh) # build/jaspr/ 정적 사이트
(cd app/app && flutter run -d linux) # Linux 호스트에서 실행
개발 중에는 아래처럼 실행한다. Flutter 앱은 r로 핫 리로드하며 Serverpod와
Jaspr는 파일 변경을 자동으로 감지한다.
(cd backend/sonax_care_server && fvm dart pub global run serverpod_cli:serverpod_cli start --no-tui --no-docker)
(cd site && fvm dart run jaspr_cli:jaspr serve --port 8083 --proxy-port 5568)
Android 실기기는 adb reverse tcp:8080 tcp:8080 후
flutter run -d <device-id> --dart-define=API_URL=http://localhost:8080/로 연결한다.
care 기능은 예약·태블릿 접수·전자서명·점주 예약 운영·CRM·캠페인 시뮬레이션·매장 설정을 제공한다. 서버의 매장별 트랜잭션과 역할별 세션을 사용한다. PRD와 브랜드 기준에 동작과 디자인 근거를 기록했다.
공개 앱의 API 기본값은 https://sonax-demo.laputa.im/이다. 배포 구성과 지속성은 infra/demo, CI 공급 변경과 검증 재사용은 CI 개선 기록을 참조한다.