Story 9.3.b-rel — мутация выкладки нового релиза ранее зарегистрированного
пакета. Прокидывает manifest + версию + tarball_sha256 на ca-admin
POST /v1/admin/releases, который сам валидирует manifest Zod-схемой
(PackageManifestSchema) и подписывает on-chain apps::setrelease от
имени chairman'а кооператива-оператора каталога.
Backend (apps-catalog-proxy):
- PublishReleaseInputDTO / PublishReleaseResultDTO — GraphQL Input/Object;
manifest как GraphQLJSON (произвольная структура валидируется на
ca-admin'е).
- AppsCatalogHttpService.createRelease(): POST /v1/admin/releases;
discriminated outcome — applied (с опциональным transaction_id) |
invalidManifest (HTTP 422 INVALID_MANIFEST мапится явно, чтобы UI
показывал валидационную ошибку отдельно от network/auth) | failed
для прочих.
- AppsCatalogProxyResolver.publishRelease(): @AuthRoles(['chairman']).
Под архитектуру E10 manifest содержит ссылки на артефакты в Nexus:
- coopenomics.backend.image — docker-image (subgraph расширения);
- coopenomics.frontend.tarball — npm tarball (desktop-bundle).
Сам manifest НЕ валидируется на стороне controller'а — это делает
ca-admin (там Zod-схема и она же будет расширена для package.json
полей в story 10.7).
Frontend (форма Опубликовать релиз) — отдельной story 9.3.b-ui после
codegen'а Zeus.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Story 9.3.b-pub стола разработчика — мутация для воскходовского
chairman'а: координаты пакета (@scope/name + owner + compatible_subnets)
прокидываются на ca-admin POST /v1/admin/package, который сам
подписывает on-chain action apps::regpkg от имени chairman'а
кооператива-оператора каталога.
Backend (apps-catalog-proxy):
- PublishPackageInputDTO / PublishPackageResultDTO — GraphQL InputType +
ObjectType (camelCase для Zeus, без snake-case утечек).
- AppsCatalogHttpService.registerPackage(): POST на /v1/admin/package;
discriminated outcome (applied | conflict | failed) — мапит 409 в
conflict, прочие axios-ошибки в failed; degraded-mode (без
APPS_CATALOG_API_KEY) возвращает failed с явным сообщением, чтобы UI
не вводился в заблуждение «опубликовано».
- AppsCatalogProxyResolver.publishPackage(): @UseGuards(GqlJwtAuthGuard,
RolesGuard) + @AuthRoles(['chairman']) — мутация доступна только
председателю кооператива.
Multipart upload install.js под архитектуру E10 НЕ делаем: расширения
теперь публикуются как npm-package + docker-image в Nexus отдельной
процедурой (`npm publish` / `docker push`) через scoped JWT от
CA-auth. Эта мутация — только on-chain маркер «такой пакет
существует»; релизы с manifest'ом — story 9.3.b-rel.
schema.gql / Zeus client — codegen за пределами этого PR (mono-каноном
pnpm generate-schema + generate-client + sdk build делается отдельным
коммитом, чтобы PR с backend changes не зависел от пересборки SDK
артефактов).
Frontend — отдельной story 9.3.b-ui: DeveloperPublishReleasePage.vue
сейчас заглушка, форму подключим после codegen'а Zeus.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Epic 10 Story 10.2b — минимальный subgraph на extension-sdk доказывает что
Federation v2 driver + JWT module + healthcheck реально работают вместе.
Smoke результат (SUBGRAPH_PORT=33001 JWT_SECRET=test-secret COOPNAME=voskhod):
- GET /_health → {"status":"ok","version":"unknown","uptimeSeconds":N}
- POST /v1/graphql { helloFromExtension(name:"мир") { greeting fromExtension } }
→ {"greeting":"Здравствуй, мир!","fromExtension":"hello-subgraph@0.1.0"}
- POST /v1/graphql { _service { sdl } } → returns federation v2 SDL с
@key/@external/@requires/@shareable директивами
Также в этом коммите:
- extension-sdk переключён с unbuild на обычный tsc (unbuild esbuild не
поддерживает декораторы для target ES2020). main: dist/index.js, CommonJS.
- @types/passport-jwt в devDeps.
Cross-extension entity references (SharedCooperator @key + ResolveField)
живут в chatcoop subgraph'е (story 10.8) — там есть локальная сущность
ChatThread которая ссылается на пайщика. Этот PoC только доказывает
базовую механику.
Epic 10 Story 10.1 — coopback становится первым federation v2 subgraph'ом.
Локальная core-схема компилируется с federation директивами (`autoSchemaFile.federation: 2`).
Endpoint остаётся на /v1/graphql; non-federated клиенты (desktop) не должны
заметить переключения — Apollo Federation добавляет лишь служебные
`_service { sdl }` и `_entities` query.
Gateway-режим (Apollo Gateway + IntrospectAndCompose) зайдёт отдельной
story 10.1b — поверх этого subgraph'а + registry в Postgres. На MVP пока
один subgraph, gateway не нужен.
Зависимости:
- + @apollo/subgraph ^2 (peer для federation 2 в @nestjs/apollo)
@key директивы на core entity (User/Cooperative/Account/Wallet) добавятся
по мере вытаскивания первых extension'ов — отдельными точечными PR.
Новый push в PR отменяет ещё бегущий typecheck той же ветки —
прежде 30-минутные прогоны копились очередью.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mono controller:
- AppsCatalogHttpService.fetchInstallScript(scope, name) — fetch text
install.js из ca-admin /v1/public/packages/:scope/:name/install.js;
degraded mode (null) при отсутствии env-config'а apps-catalog'а.
- Новый REST controller AppsCatalogInstallScriptController
`GET /v1/apps-catalog/install/:scope/:name` под HttpJwtAuthGuard:
валидирует scope/name regex'ом, проксирует на ca-admin, отдаёт
text/javascript + Cache-Control: no-store. Admin-API key остаётся
на backend'е.
- AppsCatalogProxyModule: добавлен controller в module.controllers[].
mono desktop:
- remote-loader.ts: реальный fetch + CJS-eval. fetchInstalledRemotePackages
читает Queries.Extensions.AppsCatalogRemotePackages, парсит packageId
на (scope, name). loadRemoteExtension качает install.js по REST через
axios responseType:'text' с JWT desktop'а, оборачивает в
`new Function('module','exports','require', code)`, вызывает
`module.exports.install()` → IWorkspaceConfig[].
- Try/catch per item (NFR15-стиль): падение одного пакета не валит
остальные. require() сразу throw — пакеты должны быть CJS без
внешних зависимостей (sandbox — Story 11.x).
- sha256 пока не проверяем (release-таблица будет в 9.4.c).
Demo path: voskhod publishes @voskhod/demo-app → ca-admin отдаёт
examples/demo-app/install.js → controller-proxy → desktop boot тянет +
eval'ит + получает workspace «Demo» с роутом demo-hello.
При nullable=true декоратор @Field NestJS GraphQL не умеет вывести тип из
TS-reflection-метаданных (TS пишет в metadata-ключ Object). Без явного
type fn (() => String) — UndefinedTypeError на старте.
Фикс:
- @Field({ nullable: true }) → @Field(() => String, { nullable: true })
для lastActiveVersion: string | null.
- .env.example добавлены APPS_CATALOG_URL и APPS_CATALOG_API_KEY с
готовыми значениями для dev (URL = DNS-имя ca-admin в mono-shared
сети).
Verify: coopback стартует чисто (Nest application successfully started),
DTO виден через introspection (__type AppsCatalogRemotePackageDTO →
поля packageId/publisher/title/description/rubPerMonth/lastActiveVersion/
compatibleSubnets), guard срабатывает (без JWT → 401 Unauthorized).
Story 9.5.b mono часть: добавляет модуль расширения
`apps-catalog-proxy` с GraphQL Query.appsCatalogRemotePackages, который
проксирует запрос на apps-catalog/ca-admin /v1/public/packages.
Архитектурно — выбран альтернатива C (см. apps-catalog
docs/epics/E9-demo-app-pilot.md, V2 follow-ups): браузер ходит в каталог
через mono controller, а не напрямую в ca-admin. Поэтому:
- admin-secret apps-catalog'а живёт в env controller'а, не в SPA;
- нет CORS — desktop запрос на свой же origin;
- единый канон mono GraphQL (никаких raw HTTP в desktop).
Компоненты:
- AppsCatalogRemotePackageDTO (@ObjectType): packageId/publisher/
compatibleSubnets/lastActiveVersion + UI-поля title/description/
rubPerMonth (в V1 description и rubPerMonth — заглушки; в V2 будут из
package manifest и apps-catalog pricing).
- AppsCatalogHttpService: axios-клиент к ca-admin с Authorization: Bearer
admin-key из APPS_CATALOG_API_KEY. Degraded mode (пустой список без
ошибки), если env пустой — чтобы dev/CI без apps-catalog не валили
desktop boot.
- AppsCatalogProxyResolver: @Query, @UseGuards(GqlJwtAuthGuard) — каталог
видят только авторизованные пайщики.
- Регистрация в ExtensionsModule.
Config:
- Новые env APPS_CATALOG_URL и APPS_CATALOG_API_KEY (оба optional).
- Зеркальные поля в config.apps_catalog.{url, api_key}.
TODO (требует ручного запуска на остановленном контейнере, см. CLAUDE.md:
'pnpm generate-schema / generate-client — не запускать локально'):
- pnpm -F controller generate-schema → пересоздать schema.gql.
- pnpm -F controller generate-client → graphql-zeus в SDK.
- pnpm -F sdk build → unbuild dist.
Без codegen фронт ещё не сможет дёрнуть резолвер через Zeus.
Секция «Удалённые пакеты» с карточкой @voskhod/demoapp (publisher,
price 1000 ₽/мес) + dialog «Подписаться» (V1 — заглушка).
Why: Анна видит реальный второй раздел, отделённый от bundle-расширений,
и проходит UX-сценарий клика «Подписаться» — без backend-резолверов.
Реальный список remoteExtensions через Query.appsCatalogRemotePackages
+ Mutation.appsCatalogSubscribe — отложено в 9.5.b (см. mono#64
follow-ups).
Стили — токены MONO Design System (--p-*), никаких magic hex.
V1 минимум для UI ExtensionStore:
- Информационный banner сверху grid'а карточек, объясняющий что bundle-
extensions ниже бесплатные, а remote-пакеты с подпиской появятся
на следующем шаге пилота.
- Реальная карточка remote-пакета с кнопкой «Подписаться» / «Уже
подписан до DD.MM» — story 9.5.b (требует расширения extStore
на kind/subscribed/pricingRubPerMonth + backend-резолверы coopback'а).
Refs: docs/integration/native-to-catalog-research.md §9 story 9.5.
9.3 — components/desktop/extensions/developer/:
- install.ts: workspace `developer`, defaultRoute=developer-my-packages,
4 страницы (packages, publish, subscribers, pricing), все under
meta.roles=['chairman']. Регистрация в extensions-registry.ts.
- pages/: V1 Vue-заглушки с описанием TODO (без реальных мутаций).
Mutation flow (POST /v1/admin/package, /releases, /pricing с подписью
chairman'а) — story 9.3.b.
9.4 — processes/init-installed-extensions/remote-loader.ts:
- Контракт fetch/load для remote-пакетов с активной подпиской: тип
RemoteExtensionDescriptor, функции fetchInstalledRemotePackages,
loadRemoteExtension, installRemoteExtensions.
- V1 — заглушка возвращающая []. Не падает, логирует пропуск.
- index.ts: после bundle-pass — remote pass через installRemoteExtensions;
try/catch чтобы падение remote-flow не валило bundle extensions.
- TODO 9.4.b: реальный fetch к coopback + tar-x + dynamic require
+ sha256-check + (V2 sandbox).
Видимость стола: пока через meta.roles в install.ts. Полноценная
авторизация через DesktopWorkspace.grants с флагом is_operator_coop —
9.3.b/9.5.
Refs: /home/admin/mono-ai-5/docs/integration/native-to-catalog-research.md §9.
Финальные решения по V1 пилоту (без миграции native):
- demo-app = пустой Hello-стол, @voskhod/demo-app
- Pricing 1000 RUB/мес (60 сек на dev), trial 15 мин
- partner1 как покупатель через pnpm boot:extra (на той же цепи)
- Self-subscription bypass: воскод как оператор подписан бесплатно
- Без кэша tarball'ов — каждый desktop boot качает заново
- Стол Разработчика = новый bundle-extension developer, виден chairman оператора
- partner1 desktop+coopback в отдельном docker overlay из apps-catalog
Epic 9 раскладка на 8 stories с зависимостями. Реализация — следующими
коммитами в зонтичной ветке apps-catalog mono + feat/E9-demo-app-pilot
в apps-catalog репо.
html2pdf.js на верхнем уровне модуля трогает `self` → при SSR-бандле Quasar
падает [Quasar] boot error: ReferenceError: self is not defined. Экспорт PDF
нужен только на клиенте по действию пользователя — переносим импорт в
dynamic import() внутри exportFormToPdf, сервер модуль не вычисляет.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Были перекрещены относительно реестра документов: draft 3=PrivacyPolicy,
4=UserAgreement (и тексты on-chain drafts, и cooptypes Registry, и protocol-vars
совета это подтверждают). Только make_base_coagreements крестил user↔privacy.
Затрагивает вновь создаваемые коопы; существующим нужна отдельная миграция.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Документ остаётся в скроллящемся body BaseDialog, кнопка вынесена в слот
footer (.base-dialog__foot, вне overflow) — устраняет залипание, когда до
кнопки нельзя домотать (стек maximized-диалогов RequireAgreements).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Дефолт реестра совета был newresolved → status=Resolved → виден только
утверждённый документооборот, submitted-документы (в т.ч. свежеподанные
заявления) пропадали. Переключатель вкладок на странице скрыт
(showFilter=false), сменить вид было нельзя. Выравниваем с реестром пайщика:
newsubmitted → status не ограничивается → совет видит ВСЕ документы кооператива.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
freedecision эмитит newresolved и newdecision по одному package в одной
транзакции — оба @OnEvent-хендлера срабатывают одновременно. safeReassemble
(newdecision/newact/newlink) пере-upsert'ил все строки пакета ПОЛНЫМ набором
колонок со сталым row.status, прочитанным до коммита newresolved → затирал
resolved обратно в submitted. Решение совета застревало в submitted и пропадало
из реестра совета (дефолт «Только утверждённые» = status=Resolved).
Пересборка теперь обновляет ТОЛЬКО агрегат-производные колонки
(document_aggregate/full_title/content_text/signers_text) точечным UPDATE.
status/block_num/source_action_data принадлежат статус-событиям и пересборкой
не трогаются → конкурентный newresolved не откатывается. Новый метод
SignedDocumentRepository.updateAggregate + SignedDocumentAggregateUpdate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Предыдущий фикс снимал лоадер, но onSelectWorkspace пушил на mainRoute.name —
родительский layout-роут без компонента → серый экран. WorkspaceSwitcher
работает потому что зовёт goToDefaultPage(), который через getDefaultPageRoute()
резолвит реальную лист-страницу (authorized_default_route / defaultRoute /
первый child) И сбрасывает isWorkspaceChanging. Используем его же.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Кнопка «Утвердить» совета слала authorize+exec напрямую из браузера ключом
председателя — при HTTP 504 от ноды ошибка терялась в браузере, бэкенд её не
видел и не логировал. Переводим действие на проведение через контроллер ключом
кооператива, как остальные soviet-мутации (sendAgreement) — теперь ошибки связи
с блокчейном попадают в логи бэкенда.
Контракт soviet: authorize/exec require_auth(chairman/executer) → require_auth(coopname).
Согласие председателя не теряется — закреплено его личной подписью на document2
(verify_document_or_fail) + проверкой is_valid_chairman.
- contracts: authorize.cpp/exec.cpp — require_auth(coopname)
- controller: новая мутация authorizeDecision (DecisionAuthorizeModule) + порт/адаптер,
пушит authorize+exec одной транзакцией ключом кооператива из vault
- cooptypes: authorizations actor chairman/username → coopname
- sdk: Mutations.Decisions.AuthorizeDecision + регенерация schema.gql/zeus
- desktop: фича AuthorizeAndExecDecision и process-decisions зовут бэкенд-мутацию
вместо useGlobalStore().transact; подпись документа остаётся клиентской
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
selectWorkspace() ставит isWorkspaceChanging=true (full-page WindowLoader
поверх router-view), а сбрасывал флаг только goToDefaultPage(). Обычное меню
(WorkspaceSwitcher) зовёт goToDefaultPage и лоадер снимается; command palette
(onSelectWorkspace/onSelectPage) делал прямой router.push без сброса флага —
лоадер висел вечно. Тот же leak в ConnectionAgreementStepper.goToQuickClient.
Снимаем флаг через setWorkspaceChanging(false) в .finally() после навигации.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Глобальный ::selection был --p-primary-soft (teal 10% alpha) — болотный,
почти невидим. Меняем на solid --p-accent + белый текст: видно на любом фоне.
Тост-специфичные ::selection-override (.q-notification/.toast) убраны —
accent читаем и на тёмной поверхности тоста, выделение единое везде.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Страницы deep-link (карточка документа, name=document-details/
user-document-details) помечены meta.hidden=true и в навигацию не
попадают — иконка им не нужна. Drawer рендерит q-icon под v-if='item.icon',
undefined безопасен. Убирает TS2741 'Property icon is missing'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Карточка реестра вела на страницу документа, передавая rawDocument.hash — а это
doc_hash (хеш самого документа, без подписи). Бэкенд же ищет документ по колонке
hash (подписанный документ = содержимое + подпись): UPPER(d.hash)=UPPER(:hash).
Значения разные → getDocuments возвращал 0 → «Документ не найден».
Из поиска работало, потому что searchDocuments отдаёт именно hash (колонку), а не
doc_hash. Карточка теперь берёт тот же document.hash (= колонка hash в реестре).
Проверено по БД: фильтр по doc_hash → 0 строк, по document.hash → 1; у всех
записей document.hash == колонка hash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Реестр документов переведён с таблицы на канон плоские строки (DocumentRow):
строка кликабельна и ведёт на отдельную страницу документа. Таблица с
инлайн-раскрытием оставлена только для Union-страницы (expand=true).
- DocumentCardsList: список DocumentRow (заголовок, дата, ФИО подписантов,
скачивание в actions), скелетоны при загрузке, «Загрузить ещё». Статус-чип
не показываем — в реестре всё подписано (канон stop-signal).
- ListOfDocumentsWidget: проп expand — карточки по умолчанию (совет/пайщик),
таблица для Union (передаёт expand=true) — поведение Union не меняется.
- useDocumentNavigation перенесён из фичи поиска в entity Document
(переиспользуют и поиск, и карточный список — чистые слои).
- DocumentDetailsPage: кнопка «назад» теперь в липком баре (sticky,
top=--p-topbar-h), видна всегда при прокрутке документа.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
activeWorkspaceName инициализируется асинхронно — при холодном переходе по
прямой ссылке на документ совета скоуп мог упасть в скоуп пайщика и документ
«не находился». Имя роута (document-details / user-document-details) доступно
сразу и детерминированно.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Прошлый подход (фильтр реестра по ?document=<hash> + баннер) отклонён —
нужна полноценная отдельная страница документа с переходом назад и deep-link.
- DocumentDetailsPage: canon-страница по эталону карточки собрания (back-link
под шапкой, имя документа в заголовок через setPageTitleOverride, скелетон,
EmptyState; контент — ComplexDocument). Грузит документ сама по hash из роута,
поэтому открывается и прямым переходом по ссылке.
- Роуты documents/:hash в обоих расширениях: document-details (совет) и
user-document-details (пайщик), hidden — по аналогии с meet-details.
- DocumentModel.loadDocument(username, hash): точечная загрузка одного документа
(статус-агностичный фильтр newsubmitted), не трогает состояние списка.
- Клик в поиске ведёт на страницу документа (useDocumentNavigation выбирает роут
по рабочему столу), а не фильтрует реестр.
- Откат фильтр-подхода: удалены SearchedDocumentBanner и useDocumentRouteFilter,
страницы реестра/виджет/таблица возвращены к baseline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Клик по результату поиска вёл в никуда (openDocument был пуст) — найденный
документ нельзя было посмотреть. Теперь клик кладёт hash в ?document=<hash>
текущего роута; страница реестра читает его и сводит список к одному документу,
авто-раскрывая его. Переиспользуем готовый backend-фильтр filter.document.hash
(тот же путь, что у Union-страницы) — без изменений API и codegen.
- useDocumentRouteFilter/useDocumentNavigation: чтение и установка ?document.
- SearchedDocumentBanner: баннер «показан один документ» с возвратом к списку.
- Обе страницы реестра (совет + пайщик) фильтруют виджет по выбранному документу.
- Виджет реагирует на смену filter и форсит статус-агностичный newsubmitted при
hash-фильтре (иначе на вкладке «только утверждённые» неутверждённый документ
из поиска не нашёлся бы).
- DocumentsTable авто-раскрывает строку по expandHash (регистронезависимо).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В экране ожидания регистрации (WaitingRegistration) «до 24 часов» — неактуально:
реальный регламентный срок рассмотрения советом — до 30 дней (обычно решение
принимается за день-два). Поправили текст под фактический срок.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Большая пустая полоса между строкой поиска и первым результатом мешала —
снят верхний padding секции результатов (q-pt-none), список начинается ближе.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поиск показывал username («ant») и сырой full_title с хвостом ` - - дата.pdf`.
Приводим к виду реестра на главной:
- SearchResult.signer — ФИО подписанта-субъекта («Иванов Иван Иванович»), берётся
из signer_certificate готового агрегата (подпись с signer===username, иначе первая;
организация → short_name). Тот же источник, что у чипов подписей в реестре.
- full_title в выдаче поиска теперь = meta.title (чистое наименование), как в реестре;
fallback — прежний full_title.
- Фронт DocumentSearchDialog: заголовок + чип ФИО (BaseBadge) + дата вместо строки
«username · дата»; убран пустой highlights-блок.
Схема + zeus-клиент перегенерированы (generate-schema/generate-client + sdk build).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Документы хранят дату в локализованном формате `DD.MM.YYYY HH:mm` + отдельное
meta.timezone (так пишет фабрика @coopenomics/factory через moment.tz). Прежний
`new Date(meta.created_at)` этот формат не парсил → Invalid Date → колонка
timestamptz падала с "invalid input syntax ... 0NaN-NaN-NaN", и throw в одной
записи ронял весь прогон backfill. Парсим тем же moment.tz, что и фабрика
(timezone из меты, иначе config.timezone), невалидное → null. Плюс per-record
try/catch в backfill: одна битая запись логируется и пропускается, а не валит прогон.
Проверено вживую на voskhod: backfill завершён scanned=8 created=4 updated=4
skipped=0, document_created_at = 2026-05-28 07:53:00+00 (валидные даты, не null).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TypeORM @Index(['...']) принимает имена СВОЙСТВ сущности, а не db-колонок.
Колонка объявлена @Column({ name: 'package' }) packageHash, поэтому индекс
['coopname','package'] падал на старте: "Index contains column that is missing
in the entity (SignedDocumentEntity): package". Индекс по db-колонке `package`
строится корректно и через имя свойства packageHash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ошибки цепи возвращают суммы с сырой precision=4 ("100.0000 RUB"). Добавлен formatAssetsInText() рядом с formatAsset2Digits и применён в FailAlert — единая точка для всех тостов ошибок. Суммы вида <целое>.<>=3 знаков> <ТИКЕР> приводятся к 2 знакам с группировкой; уже отформатированные не трогаются (идемпотентно).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
theme boot подписывал watch на глобальный Quasar-реактив Dark. В SSR boot-файлы
выполняются на каждый рендер-запрос, а watch без owning-scope не утилизируется —
подписчики копились на синглтоне Dark безгранично (та же природа, что у sentry.ts,
только дешевле за итерацию). Тема (data-theme на documentElement) нужна только на
клиенте; гейтим boot через process.env.SERVER.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Boot-файл sentry.ts использует браузерный @sentry/vue с browserTracingIntegration.
В SSR boot-файлы выполняются на каждый рендер-запрос, поэтому Sentry.init плодил
новый клиент/интеграции на каждом запросе (утечка памяти → «пила» RAM, заметная
на нодах с малым объёмом RAM) и спамил серверный лог строкой про инициализацию
(~80/мин). Гейтим boot через process.env.SERVER (как в chatwoot.ts) — Sentry
остаётся только на клиенте, где ему и место.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Глобальный ::selection красит текст в var(--p-ink) (тёмный на свет-теме)
поверх полупрозрачного фона — на тёмной поверхности тоста выделенный текст
пропадал. Scoped-override для .q-notification/.toast: полупрозрачная светлая
подложка + светлый текст, читаемо на всех типах тостов.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Баг A: предупреждение "Failed to locate Teleport target '#header-actions-host'"
возникало, потому что host был под v-if='loggedIn'. Сразу после регистрации
навигация на стол происходит раньше, чем loggedIn (зависит от async
session.isAuth / isRegistrationComplete) станет true → цель Teleport
отсутствует в DOM → даже defer-Teleport источников страниц её не находит.
Рендерим #header-actions-host безусловно — видимость блока действий уже
регулируется CSS (.topbar__actions:has(> .header-actions-host:only-child:empty)).
Баг B: после регистрации grant-gated кнопки («Совершить взнос», «Получить
возврат») не появлялись до F5. В потоке завершения регистрации (watch на
participant_account) не вызывался desktops.loadDesktop() (свежие столы/гранты),
а кошелёк/agreements не грузились: на момент первого run() статус ещё не
'active' → isFullyActive=false. После того как статус обновился, до-вызываем
run(true) и loadDesktop() по образцу init-app / EnableButton. Условие
isFullyActive в init-wallet не ослаблялось.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Только неочевидное поле — наименование банка получило подсказку ПАО "Сбербанк".
Остальные поля ИП самоописательны (формат даты, кол-во цифр, «как в паспорте»).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Поля краткого/полного наименования, должности представителя и наименования
банка получили placeholder-подсказки (ПК "Ромашка", Председатель совета,
ПАО "Сбербанк") — показываются при фокусе пустого поля.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hover-правило .base-radio-card:hover (специфичность 0,2,0) перекрывало
.base-radio-card--selected (0,1,0), поэтому при наведении на выбранную
карточку программы primary-фон/граница подменялись на surface/soft и
выбор визуально пропадал. Ограничил hover невыбранными карточками через
:not(.base-radio-card--selected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Заголовок workspace-row получал outline только когда стол активный
(.is-active .row.is-selected). На неактивных столах при навигации курсор
«терялся»: только лёгкая смена фона на surface-2, без рамки — в отличие
от page'ей, которые подсвечиваются outline'ом всегда. Выровнял правило:
outline на любом .workspace-row.is-selected.
На верхней/нижней позиции стрелка дальше прыгала в противоположный
конец списка из-за (idx+delta+len)%len. Заменено на clamp в [0..len-1]:
курсор остаётся на месте, как в нативном listbox/меню. Пустое выделение
+ ↓ ставит на первый, ↑ — на последний.
Активный workspace был position:sticky top:0 — при навигации стрелкой
вверх курсор-выделение «исчезал» за sticky-overlay'ем, потому что
scrollIntoView({block:'nearest'}) считал элемент видимым в normal flow,
не учитывая sticky-перекрытие. Активный стол и так идёт первым по
сортировке + есть badge «Активный», sticky не нужен.
- CommandPalette.vue: все --p-accent / --p-accent-soft / --p-ink-on-accent
заменены на --p-primary (бирюзовая палитра канона). Откатывается
регрессия после fix-коммитов design-wave1 (sticky-banner / clamp /
рамка курсора), которые случайно вернули accent поверх primary
(см. b82b778b81 — изначально palette был в primary).
- default.vue::paletteWorkspaces: возвращена фильтрация meta.conditions
(participant/chairman/soviet install.ts) + meta.hidden (capital
install.ts) — та же логика что в LeftDrawerMenu.filteredRoutes,
чтобы CommandPalette не показывал страницы, которые не отображаются
в rail-меню. onSelectWorkspace/onSelectPage закрывают левый дровер
на мобильниках через desktop.closeLeftDrawerOnMobile().
- layouts/default.vue: вместо widgets/Desktop/CmdkMenu mount'им
shared/ui/domain/CommandPalette (canon из design-wave1) с адаптером
workspaceMenus → CommandPaletteWorkspace[] и глобальным ⌘K / Ctrl+K.
- entities/CommandPalette: минимальный store (isOpen / open / close /
toggle) — заменяет тяжёлый useCmdkMenuStore.
- LeftDrawerMenu.onCmdk → palette.open() (триггер «Найти» в AppDrawer).
- Удалены целиком осиротевшие после design-wave1 директории:
entities/CmdkMenu, widgets/Desktop/CmdkMenu, widgets/Desktop/CmdkTrigger,
widgets/Desktop/SecondLevelMenuList (никем не использовался),
widgets/Desktop/WorkspaceMenu (заменён на WorkspaceSwitcher),
widgets/Wallet/MicroWallet (заменён на RailUserCard).
- Поправлены barrels widgets/Desktop и widgets/Wallet.
Фон и текст тоста (.q-notification + .toast) теперь фиксированные тёмная
поверхность + светлый текст в обеих темах, а не инвертируемые
var(--p-ink)/var(--p-canvas). На dark-теме текст уходил в тёмный на тёмном
deep-tint фоне (#052e16 и т.п.) — нечитаемо. Цветные иконки по типу
остаются на токенах.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
q-pa-lg на flat-карточке SignAgreementDialog — документ больше не прижат
к границе на странице кошелька пайщика.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Глобальный Omit<IPayment,'id'>&{id:string} ломал store.ts/mergePayments
и features/Payment/SetStatus/api (туда приходит сырой Zeus-вывод с
id:unknown). Откатываю override в entities/Payment/model/types.ts,
сужаю id до string локально в ListOfPaymentsWidget через IPaymentRow —
точка сужения только в UI, контракт стора/API не трогается.
vue-tsc desktop локально: PASS.
QuestionsTable принимал Cooperative.Document.IComplexAgenda[] (cooptypes,
table:IDecision без certificates, id:IUint64=number|string), а сверху
ListOfAgendaQuestions передаёт IAgenda[] из desktop/entities/Agenda
(GraphQL-вывод getAgenda, table:BlockchainDecision с certificates,
id:number). Перевожу QuestionsTable на IAgenda — это реальная форма
данных из стора; уходит расхождение IComplexAgenda↔IAgenda для
:agenda='row' в QuestionCard (он тоже IAgenda).
IPayment.id был ModelTypes['ID']=unknown (Zeus не имеет scalar-resolver
для ID), из-за чего шаблон ListOfPaymentsWidget падал на expanded.get/.set
и :id-биндингах. Переопределяю id как string через Omit+&.
Ошибки цепи возвращают суммы с сырой precision=4 ("100.0000 RUB"). Добавлен formatAssetsInText() рядом с formatAsset2Digits и применён в FailAlert — единая точка для всех тостов ошибок. Суммы вида <целое>.<>=3 знаков> <ТИКЕР> приводятся к 2 знакам с группировкой; уже отформатированные не трогаются (идемпотентно).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
theme boot подписывал watch на глобальный Quasar-реактив Dark. В SSR boot-файлы
выполняются на каждый рендер-запрос, а watch без owning-scope не утилизируется —
подписчики копились на синглтоне Dark безгранично (та же природа, что у sentry.ts,
только дешевле за итерацию). Тема (data-theme на documentElement) нужна только на
клиенте; гейтим boot через process.env.SERVER.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Boot-файл sentry.ts использует браузерный @sentry/vue с browserTracingIntegration.
В SSR boot-файлы выполняются на каждый рендер-запрос, поэтому Sentry.init плодил
новый клиент/интеграции на каждом запросе (утечка памяти → «пила» RAM, заметная
на нодах с малым объёмом RAM) и спамил серверный лог строкой про инициализацию
(~80/мин). Гейтим boot через process.env.SERVER (как в chatwoot.ts) — Sentry
остаётся только на клиенте, где ему и место.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Глобальный ::selection красит текст в var(--p-ink) (тёмный на свет-теме)
поверх полупрозрачного фона — на тёмной поверхности тоста выделенный текст
пропадал. Scoped-override для .q-notification/.toast: полупрозрачная светлая
подложка + светлый текст, читаемо на всех типах тостов.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Устаревшая формулировка в docstring backfill-сервиса: после перехода на ключ
doc_hash идемпотентность/уникальность реестра — по (coopname, doc_hash), а НЕ по
(coopname, package). package сознательно не уникален (один процесс = несколько
разных документов с разными подписантами), поэтому ключом записи он быть не может.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ось упорядочивания версий подписи одного doc_hash должна мерить одну величину
на обеих сторонах guard'ов ingestAction. Раньше block_num хранился через
resolveBlockNum(meta.block_num, action.block_num) с приоритетом meta.block_num
(блок ГЕНЕРАЦИИ документа фабрикой), а incomingBlock в guard'ах — action.block_num
(блок ДЕЙСТВИЯ в цепи). Так как документ всегда генерируется до подачи в цепь
(meta.block_num <= action.block_num), защита от отката на старую версию подписи
(incomingBlock < existing.blockNum) и идемпотентность по блоку фактически не
срабатывали. Теперь приоритет — action.block_num, meta.block_num лишь fallback.
Заодно выправлены устаревшие комментарии интерфейса репозитория (ключ — doc_hash,
а не hash).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Два уточнения по замечаниям:
1. Ключ реестра переведён с hash на doc_hash. doc_hash = идентичность документа; hash включает
наложенные подписи, поэтому у ОДНОГО документа бывает несколько hash (версии по мере накопления
подписей — напр. акт приёма-передачи: 1-я подпись → один hash, 2-я → другой, doc_hash тот же).
При ключе по hash документ задваивался в списке (одна подпись / две подписи). Теперь:
- уникальный индекс (coopname, doc_hash); hash и package — неуникальные индексы;
- ingestAction ключуется по data.document.doc_hash и держит КРАЙНЮЮ версию: guard'ы «не понижать
статус» и «не откатывать на меньший block_num» (getState отдаёт status+blockNum); крайняя версия
перетирает предыдущую (тот же документ, больше подписей);
- hash хранится как версия последней подписи (для фильтра Union по document.hash и отображения).
package по-прежнему группирует разные документы процесса (обмен = разные doc_hash = разные строки).
2. searchDocuments вернул доступ пайщику, но со скоупом: член совета (chairman/member) ищет по всему
кооперативу, обычный пайщик — ТОЛЬКО по своим документам (search scope username=user.username).
Снял role-гард, добавил @CurrentUser; SearchResolver решает скоуп по роли. Заодно закрывает прежнюю
утечку (старый поиск был coop-wide для любого авторизованного).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Самоаудит по замечанию: package — это идентификатор ПРОЦЕССА, в который может входить несколько
заявлений с разными подписантами. Доказано в контракте: marketplace/change.cpp шлёт ДВА newsubmitted
с одним package, но разными document и username (обмен: contribute+return). Прежний уникальный индекс
(coopname, package) + upsert-по-package затирал второе заявление первым → ПОТЕРЯ ДОКУМЕНТА и склейка
username. Старый explorer-путь (список по newsubmitted) показывал оба.
- Сущность: уникальный индекс теперь (coopname, hash) — одна строка = один документ-заявление;
(coopname, package) стал НЕуникальным группирующим индексом.
- ingestAction ключуется по data.document.hash; смена статуса (newsubmitted/newresolved/newdeclined,
все несут то же заявление — подтверждено createagenda/declinedoc/make_complete_document) матчит
строку по hash.
- newdecision/newact/newlink несут package (не заявление) → пересобирают агрегаты ВСЕХ заявлений
пакета (repository.findByPackage), сохраняя статус каждого.
- Репозиторий: upsert/getStatus по hash; findByPackage вместо getSourceActionData; убраны
неиспользуемые setStatus/exists.
Также: searchDocuments закрыт гардом RolesGuard + @AuthRoles(['chairman','member']) — поиск по
документообороту кооператива только для совета (SearchDocumentsInput не содержит username, поэтому
правило «свои ресурсы» не открывает доступ обычному пайщику). Поиск пайщиком по своим документам
пока не предусмотрен.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Самоаудит видимости документов выявил две регрессии относительно прежнего explorer-пути:
1. Вкладка «Все входящие» (type=newsubmitted) теряла решённые документы. Прежний фильтр был по
ИМЕНИ действия: у каждого пакета есть newsubmitted-действие → «Все входящие» = все документы
(решённые показывались и тут, и во «Только утверждённые»). Партиция по статусу (newsubmitted→
status=Submitted) скрывала решённые из дефолтной вкладки User/DocumentsPage. Фикс: newsubmitted →
без фильтра статуса (все), newresolved → status=Resolved.
2. Backfill сканировал explorer с query={} — без скоупа. Explorer (cooparser) хранит трейсы по
получателям; без receiver вернулись бы все трейсы каждого действия (soviet/пайщик) и, возможно,
чужие кооперативы. Фикс: query={receiver: coopname} — ровно как прежний chairman-путь getDocuments
(один трейс на документ, только наш кооператив).
Утечки не было и нет: RolesGuard разрешает обычному пайщику только data.username==его_имя, а фильтр
findAggregates ограничивает username; coopname-скоуп (весь кооператив) доступен лишь совету/председателю.
findAggregates всегда фильтрует по config.coopname.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Завершение перехода одним PR: searchDocuments и getDocuments уже читают PG-реестр подписанных
документов, OpenSearch стал мёртвым кодом — убираем его полностью.
- Удалены infrastructure/search/* (opensearch.service, search-registry.service,
search-infrastructure.module) и application/search/services/search-event.service
(старый индексатор по newsubmitted — заменён SignedDocumentIngestionService + backfill).
- app.module: снят SearchInfrastructureModule. search.module: только SearchResolver (читает
SIGNED_DOCUMENT_REPOSITORY из глобального TypeOrmModule).
- package.json: убрана зависимость @opensearch-project/opensearch; pnpm-lock.yaml пересчитан.
- docker-compose: удалён сервис opensearch + volume opensearch_data.
- .env-example: убраны OPENSEARCH_* (поиск больше не конфигурируется через env).
- system.interactor: features.search = true (поиск на PG доступен всегда, не зависит от OPENSEARCH_ENABLED).
Файлы удалены только из текущего состояния (git rm) — в истории остаются.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Старый getDocuments через DocumentService подмешивал в explorer-фильтр `receiver: username`
(on-chain require_recipient): receiver=coopname отдавал все документы кооператива, receiver=<пайщик>
— его документы; Union-страница добавляла filter.document.hash. PG read-path эти ключи игнорировал
(искал несуществующий data.username) — личная страница пайщика показала бы все документы кооператива.
- interactor: extractReceiverScope (receiver===coopname → весь кооператив; иначе фильтр username=receiver,
т.к. колонка username = пайщик-субъект заявления) + extractHashFilter (filter.document.hash).
- repository: новый параметр hash в SignedDocumentListParams; фильтр UPPER(d.hash)=UPPER(:hash)
(фронт шлёт хэш в upper-case).
Набор документов getDocuments теперь совпадает со старым explorer-путём для всех трёх потребителей
(Cooperative/ListOfDocuments, User/DocumentsPage, Union/ListOfCooperatives).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Поиск по документам — в основном по фамилии подписанта, а не username. content_text (html заявления)
этого не покрывает: соподписанты (председатель/совет) подписывают решение/акты, а не тело заявления,
плюс зависимость от текста шаблона. ФИО уже присутствуют в агрегате — в signer_certificate каждой подписи
(DocumentAggregator резолвит их из учётных данных подписанта).
- Новая колонка signed_documents.signers_text — ФИО/наименования всех подписантов пакета
(last/first/middle_name физлиц-ИП и short_name организаций из signer_certificate всех частей
агрегата: заявление/решение/акты/связанные) + их username.
- Заполняется на ingestion и backfill из готового агрегата (collectSignersText).
- searchDocuments ILIKE теперь покрывает signers_text (приоритетно), full_title, content_text, username.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Фундамент C28-21: PG-проекция подписанных документов (таблица signed_documents),
наполняемая ловлей blockchain-событий soviet с внутренней шины и разовым backfill'ом.
Аддитивно — OpenSearch и резолверы (searchDocuments/getDocuments) пока не трогаются.
- SignedDocumentEntity (signed_documents): package/hash/coopname/username/status,
full_title/content_text/html/pdf(bytea), document_aggregate(jsonb), meta, block_num.
- SignedDocumentRepository (+SIGNED_DOCUMENT_REPOSITORY) + TypeORM-реализация (upsert/setStatus/search ILIKE).
- SignedDocumentIngestionService: @OnEvent(action::soviet::{newsubmitted,newresolved,newdeclined})
-> идемпотентный upsert с защитой от понижения статуса; контент из factory-Mongo.
- SignedDocumentBackfillService: разовый идемпотентный скан действий (env-gate SIGNED_DOCS_BACKFILL_ON_BOOT).
- Регистрация в TypeOrmModule + app.module.
newdeclined пока не экспортирован как registry-action в cooptypes — подписка по литералу на будущее.
Следующая итерация: перевод searchDocuments/getDocuments на PG + снос OpenSearch + тесты.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Баг A: предупреждение "Failed to locate Teleport target '#header-actions-host'"
возникало, потому что host был под v-if='loggedIn'. Сразу после регистрации
навигация на стол происходит раньше, чем loggedIn (зависит от async
session.isAuth / isRegistrationComplete) станет true → цель Teleport
отсутствует в DOM → даже defer-Teleport источников страниц её не находит.
Рендерим #header-actions-host безусловно — видимость блока действий уже
регулируется CSS (.topbar__actions:has(> .header-actions-host:only-child:empty)).
Баг B: после регистрации grant-gated кнопки («Совершить взнос», «Получить
возврат») не появлялись до F5. В потоке завершения регистрации (watch на
participant_account) не вызывался desktops.loadDesktop() (свежие столы/гранты),
а кошелёк/agreements не грузились: на момент первого run() статус ещё не
'active' → isFullyActive=false. После того как статус обновился, до-вызываем
run(true) и loadDesktop() по образцу init-app / EnableButton. Условие
isFullyActive в init-wallet не ослаблялось.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Только неочевидное поле — наименование банка получило подсказку ПАО "Сбербанк".
Остальные поля ИП самоописательны (формат даты, кол-во цифр, «как в паспорте»).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Поля краткого/полного наименования, должности представителя и наименования
банка получили placeholder-подсказки (ПК "Ромашка", Председатель совета,
ПАО "Сбербанк") — показываются при фокусе пустого поля.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hover-правило .base-radio-card:hover (специфичность 0,2,0) перекрывало
.base-radio-card--selected (0,1,0), поэтому при наведении на выбранную
карточку программы primary-фон/граница подменялись на surface/soft и
выбор визуально пропадал. Ограничил hover невыбранными карточками через
:not(.base-radio-card--selected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Заголовок workspace-row получал outline только когда стол активный
(.is-active .row.is-selected). На неактивных столах при навигации курсор
«терялся»: только лёгкая смена фона на surface-2, без рамки — в отличие
от page'ей, которые подсвечиваются outline'ом всегда. Выровнял правило:
outline на любом .workspace-row.is-selected.
На верхней/нижней позиции стрелка дальше прыгала в противоположный
конец списка из-за (idx+delta+len)%len. Заменено на clamp в [0..len-1]:
курсор остаётся на месте, как в нативном listbox/меню. Пустое выделение
+ ↓ ставит на первый, ↑ — на последний.
Активный workspace был position:sticky top:0 — при навигации стрелкой
вверх курсор-выделение «исчезал» за sticky-overlay'ем, потому что
scrollIntoView({block:'nearest'}) считал элемент видимым в normal flow,
не учитывая sticky-перекрытие. Активный стол и так идёт первым по
сортировке + есть badge «Активный», sticky не нужен.
- CommandPalette.vue: все --p-accent / --p-accent-soft / --p-ink-on-accent
заменены на --p-primary (бирюзовая палитра канона). Откатывается
регрессия после fix-коммитов design-wave1 (sticky-banner / clamp /
рамка курсора), которые случайно вернули accent поверх primary
(см. b82b778b81 — изначально palette был в primary).
- default.vue::paletteWorkspaces: возвращена фильтрация meta.conditions
(participant/chairman/soviet install.ts) + meta.hidden (capital
install.ts) — та же логика что в LeftDrawerMenu.filteredRoutes,
чтобы CommandPalette не показывал страницы, которые не отображаются
в rail-меню. onSelectWorkspace/onSelectPage закрывают левый дровер
на мобильниках через desktop.closeLeftDrawerOnMobile().
- layouts/default.vue: вместо widgets/Desktop/CmdkMenu mount'им
shared/ui/domain/CommandPalette (canon из design-wave1) с адаптером
workspaceMenus → CommandPaletteWorkspace[] и глобальным ⌘K / Ctrl+K.
- entities/CommandPalette: минимальный store (isOpen / open / close /
toggle) — заменяет тяжёлый useCmdkMenuStore.
- LeftDrawerMenu.onCmdk → palette.open() (триггер «Найти» в AppDrawer).
- Удалены целиком осиротевшие после design-wave1 директории:
entities/CmdkMenu, widgets/Desktop/CmdkMenu, widgets/Desktop/CmdkTrigger,
widgets/Desktop/SecondLevelMenuList (никем не использовался),
widgets/Desktop/WorkspaceMenu (заменён на WorkspaceSwitcher),
widgets/Wallet/MicroWallet (заменён на RailUserCard).
- Поправлены barrels widgets/Desktop и widgets/Wallet.
Фон и текст тоста (.q-notification + .toast) теперь фиксированные тёмная
поверхность + светлый текст в обеих темах, а не инвертируемые
var(--p-ink)/var(--p-canvas). На dark-теме текст уходил в тёмный на тёмном
deep-tint фоне (#052e16 и т.п.) — нечитаемо. Цветные иконки по типу
остаются на токенах.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
q-pa-lg на flat-карточке SignAgreementDialog — документ больше не прижат
к границе на странице кошелька пайщика.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Глобальный Omit<IPayment,'id'>&{id:string} ломал store.ts/mergePayments
и features/Payment/SetStatus/api (туда приходит сырой Zeus-вывод с
id:unknown). Откатываю override в entities/Payment/model/types.ts,
сужаю id до string локально в ListOfPaymentsWidget через IPaymentRow —
точка сужения только в UI, контракт стора/API не трогается.
vue-tsc desktop локально: PASS.
QuestionsTable принимал Cooperative.Document.IComplexAgenda[] (cooptypes,
table:IDecision без certificates, id:IUint64=number|string), а сверху
ListOfAgendaQuestions передаёт IAgenda[] из desktop/entities/Agenda
(GraphQL-вывод getAgenda, table:BlockchainDecision с certificates,
id:number). Перевожу QuestionsTable на IAgenda — это реальная форма
данных из стора; уходит расхождение IComplexAgenda↔IAgenda для
:agenda='row' в QuestionCard (он тоже IAgenda).
IPayment.id был ModelTypes['ID']=unknown (Zeus не имеет scalar-resolver
для ID), из-за чего шаблон ListOfPaymentsWidget падал на expanded.get/.set
и :id-биндингах. Переопределяю id как string через Omit+&.
Снижает минимальный порог состава совета кооператива на этапе
Install Coop с 5 до 3 человек. Единственный ограничитель —
контракт soviet::createboard (см. consts.hpp + createboard.cpp:58).
Backend (install.interactor) и desktop (SetSovietForm) собственных
числовых проверок не имеют — фронт требует только «хотя бы одного»,
бэкэнд передаёт массив как есть в blockchainPort.createBoard.
ResourceInfoWidget в extensions/powerup импортирует ModalBase через
barrel: import { ModalBase } from 'src/shared/ui'. После сноса
re-export'а vite валился SyntaxError: does not provide an export.
Возвращаю строку обратно — папка ModalBase уже восстановлена
предыдущим коммитом.
В прошлой волне снёс shared/ui/{ModalBase,TitleStyles,CardStyles,AutoAvatar},
но смотрел только src/. В extensions/{capital,chairman}/ осталось:
* ModalBase — 17 файлов
* TitleStyles (side-effect SCSS) — 6 страниц capital
* CardStyles (side-effect SCSS) — 1 виджет capital
* AutoAvatar — 1 страница capital
Из-за этого vite валился на 404 TitleStyles → "Failed to fetch dynamically
imported module src/boot/init.ts" и фронт не грузился.
Восстановлены оригинальные файлы. AutoAvatar в shared/ui/AutoAvatar
оставлен как re-export нового канон-расположения shared/ui/domain/AutoAvatar.
Полная миграция extensions на BaseDialog и канон-токены — отдельная волна.
При bootstrap'е фронта RequireAgreements/SignUp успевают смонтироваться
ДО завершения system.loadSystemInfo(), и info.coopname=undefined.
Zeus сериализует variables в {} → сервер кидает
"Variable $coopname of required type String! was not provided".
Решение по слою:
- api/index.ts: гард — если coopname пустой, не отправляем запрос.
- RequireAgreements/SignUp: watch(() => info.coopname) — load
отрабатывает, как только coopname прорастёт, без блокировки UI.
В BaseDialog добавлен prop :maximized (boolean) — q-dialog получает maximized, q-card растягивается на весь экран, body становится flex-scrollable. При :maximized размер size игнорируется.
Это покрывает 5 last maximized call-sites:
* Agreementer.SignAgreementDialog — fullscreen для подписи договора (hideCloseButton)
* Agreementer.ReadAgreementDialog — fullscreen для просмотра договора
* Agreementer.StaticPrivacyDialog — fullscreen для статичной privacy policy
* Branch.SelectBranch.SelectBranchOverlay — fullscreen onboarding flow с шагами 1/2
* Decision.CreateProject.CreateProjectButton — fullscreen для предложения повестки
Все 5 переведены на BaseDialog :maximized='true' :close-on-backdrop='false' :close-on-escape='false'.
После миграции ModalBase больше не используется — удалена папка shared/ui/ModalBase и реэкспорт из shared/ui/index.ts. Design-wave1 закрыт полностью.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Простые модальные диалоги переведены с q-dialog+ModalBase на canon BaseDialog (внутри уже q-dialog), API v-model:modelValue:
* features: DeleteBranch / CreateBranch / DepositToWallet (двух-стейтовый dialog) / WithdrawFromWallet / AddPaymentMethod / DeletePaymentMethod / Decision.CreateProjectFreeDecision (lg) / FreeDecision.CreateProject (md) / Payment.SetStatus.SetOrderPaid/Completed/Refunded
* widgets: ConnectionDashboard.AxonWallet / Desktop.WorkspaceMenu (size=lg вместо 700px-фикса)
* shared: CreateDialog (общий wrapper) / CouncilOnboarding.CouncilOnboardingCard (кнопки в slot footer)
Persistent → :close-on-backdrop='false' + :close-on-escape='false'. @hide='clear' → @update:model-value с проверкой v===false. style='width: NNNpx' переведён на canon size sm/md/lg.
Что НЕ мигрировалось (5 файлов остались с ModalBase):
SelectBranchOverlay, SignAgreementDialog, ReadAgreementDialog, StaticPrivacyDialog, Decision.CreateProject.CreateProjectButton — все используют :maximized='true'. BaseDialog в canon не имеет fullscreen-режима, расширять canon без согласования — нарушение правила "Канон-нейминг согласовывать ДО реализации". Эти 5 кейсов оставлены legacy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Form.vue переписан как тонкий wrapper над canon BaseForm + BaseButton с сохранением старого API (handlerSubmit/isSubmitting/showCancel/showSubmit/buttonSubmitTxt/buttonCancelTxt/disabled/size).
Что изменилось визуально:
* Кнопки cancel/submit вынесены в slot #footer (canon-layout с gap=14px между body и footer).
* Cancel — variant='ghost' (плоская), Submit — variant='primary' (canon-primary), оба BaseButton с no-caps/без ripple.
* Кнопки выровнены justify-content:flex-end (canon-pattern для форм), вместо старого .flex без выравнивания.
API call-sites не трогаем — 20 form-call-sites (Branch/Wallet/PaymentMethod/Agreementer/Decision/Union/FreeDecision/Payment/Request/AxonWallet) получают canon-визуал сразу.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Глобальный CardStyles из 430 строк CSS реально использовался только через .info-card в RestartMeetForm.vue.
* Все остальные классы (card-container, section-header, card-label, card-value) определены локально в scoped CSS соответствующих компонентов — глобальный импорт лишний.
* Стили .info-card (+ dark-override) заинлайнены в scoped стиль RestartMeetForm под нативным именем .meet-form-agenda-item.
* Удалены: глобальный импорт в App.vue, @import в RestartMeetForm, папка shared/ui/CardStyles.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* InputStyles — весь файл состоял из закомментированного кода.
* TitleStyles — .title-container не использовался ни в одном .vue.
* Удалены 3 импорта InputStyles в Editable*Card и реэкспорт TitleStyles из shared/ui/index.ts.
CardStyles оставлен: используется в 4 call-sites (RestartMeetForm, UnionMembershipStep, TariffCard, MeetInfoCard) — миграция отдельным шагом.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* ThemeToggle получает asButton/showText/isMobile, поглощая роль ToogleDarkLight (исправлена опечатка в имени).
* AutoAvatar перенесён в shared/ui/domain/AutoAvatar — он DiceBear-процедурный, а не base-примитив (canon Avatar делает инициалы/image и не заменяет процедурную генерацию).
* Снесены legacy папки shared/ui/ToogleDarkLight и shared/ui/AutoAvatar.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
В CLAUDE.md новый раздел «ДИЗАЙН-КАНОН desktop — ОБЯЗАТЕЛЕН»:
1) HTML-канон ~/blago/production/shared/MONO Design System.html;
2) Живая реализация components/desktop/src/pages/_dev/ui/index.vue.
После компакта забыл, что shared/MONO Design System.html — основной SoT,
и нашёл «канон» в auth-prototype, который НЕ канон — пришлось переверстать
тосты. Фиксирую правило, чтобы не повторять.
Завершаю аудит — добавил capital по запросу:
- RouteMenuButton получил опциональный icon-проп; на мобильном
превращается в round-иконку + tooltip, на десктопе — flat + label.
- В ProjectPage и ComponentPage прописал material-иконки для каждого
раздела (Описание/Артефакты/Компоненты/План/Участники/История/
Задачи/Голосование/Результаты).
- ImportContributorButton, ImportContributorsButton, FilterDialogWithButton
переведены в canon-micro (isMobile → flat+dense+sm+accent + tooltip).
Заодно вынес q-dialog из q-btn-вложенности.
RouteMenuButton по сути — раздельные таб-кнопки навигации; полноценное
UX-решение для capital (вынести в SecondLevelTabs под шапкой) — отдельная
задача, тут только мобильная читаемость шапки.
CSS-хак `.topbar__actions .base-btn__label { display: none }` не покрывал
эти две кнопки: они рисуют q-btn напрямую через registerAction (не через
BaseButton под .topbar__actions). В итоге на мобильном «Добавить члена»
и «добавить участок» оставались с лейблами и распирали шапку.
Перевёл обе в canon-паттерн как CreateMeetButton/DepositButton/WithdrawButton:
isMobile → flat+dense+sm+accent, иконка + q-tooltip; без обёртки
.header-action, чтобы кнопка не растягивалась на всю высоту шапки.
- quasar-canon.css: переписан override .q-notification под реальный
канон (shared/MONO Design System.html → .toast): тёмный фон
(var(--p-ink) для нейтрального; #052e16/#4c0a0a/#3d2400/#0c1e3f
для positive/negative/warning/info), светлый текст, ЦВЕТНАЯ иконка
по типу (--p-pos/--p-neg/--p-warn/--p-info). Раньше ошибочно был
сделан нейтральный фон + left-accent-полоска — «откуда попало»,
не из канона.
- alerts.ts: убран color: 'grey-7'/'positive' у actions — они
наследуют светлый тон от тёмного фона через override.
- _dev/ui секция 39 «Тосты»: пять кнопок для триггера всех типов
тостов (успех, успех с CTA, ошибка, in-app push, push с avatar).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SuccessAlert/FailAlert/NotifyAlert (shared/api/alerts.ts): единый
визуал через override .q-notification в quasar-canon.css. Нейтральная
поверхность + 3px accent-полоска по типу (positive/negative/warning
/info), без «цветной заливки карточки» (stop-signal §19.1).
- Позиция тостов bottom-right — было top-right, что противоречило
канону mono-design-system v2 (toast-host: fixed; bottom: 24px;
right: 24px). На мобильнике — full-width минус 16px полей.
- SuccessAlert: type:'positive' вместо color:'primary' — раньше успех
красился брендовым teal, а не позитивным зелёным.
- NotifyAlert: дефолтная иконка notifications, если нет avatar — чтобы
in-app push от Novu визуально совпадал с Success/Fail.
- Topbar @media (max-width:600px): .topbar__actions .base-btn__label
скрыт, остаются только иконки CTA-кнопок страницы. Бренд-название
с ellipsis (max-width: 50vw). Раньше кнопки переносились на вторую
строку на узких экранах.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI vue-tsc --noEmit падал на четырёх файлах:
- QuestionsTable.vue: props.decisions типизирован через
PropType<Cooperative.Document.IComplexAgenda[]>. Раньше тип был
плоский Array → row в v-for становился unknown, и template
ругался на row.table.id, isProcessing(row.table.id) и т.п.
- ListOfPaymentsWidget.vue: явный computed items: IPayment[] через
cast (as unknown as IPayment[]) — zeus резолвил items слабее, чем
нужно шаблону, и поля row.id / username / quantity / etc. оставались
unknown.
- ExtensionPage.vue и ExtensionCard.vue: для AutoAvatar :username
добавлен fallback || '' — extension.name / extension.title могут
быть string | null | undefined, AutoAvatar требует string.
ESLint по четырём файлам — EXIT=0; tsc-прогон делегирован CI
(точечный vue-tsc по факту тащит весь проект и вешает машину, см.
memory feedback_no_full_vue_tsc_desktop).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Без delay nodemon реагировал на каждое fs-событие пачки (правки/сборка/git на хосте) десятками рестартов в секунду; pstree.remy без `ps` в образе виснет на обходе /proc (накапливались зомби-сканеры), холодный старт ts-node прерывался, порт 2998 не занимался — бэкенд молча лежал. delay:2000 схлопывает пачку в один рестарт, signal:SIGTERM даёт чистое завершение дочернего процесса.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
extensionRepository.update заменяет config JSONB целиком; chairman/powerup/meet-tracker писали захваченный при initialize снимок поверх свежих данных. Теперь read-modify-write по актуальному config из БД с наложением только владомых полей.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Boot-файл haptics: при reload страницы на устройстве с Vibration API (мобильный
браузер / PWA на Android) коротко вибрирует (15 мс). Reload определяется через
Navigation Timing (с фолбэком на legacy API), чтобы не срабатывать на первой
загрузке/навигации. Полностью безопасно для десктопа и обычных сайтов: где
Vibration API нет — вызов не делается, любая ошибка проглатывается, на загрузку
приложения не влияет. Только клиент (гард для SSR).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
В левом дровере был маленький горизонтальный скроллбар, и трекпадом его можно
было «оттянуть», обнажая правую границу — дровер выглядел скроллируемым, хотя не
должен. Причина: у .rail собственный border-right (1px), который при content-box
даёт ~1px overflow внутри контента дровера. Фикс в .app-left-drawer: overflow-x
hidden + overscroll-behavior-x contain на .q-drawer__content, .rail приведён к
box-sizing border-box и width 100%.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Если проголосовать и сразу нажать «Утвердить», утверждение падает с ошибкой —
бэкенд ещё не учёл голос из блокчейна. После успешного голоса держим состояние
загрузки пункта (а значит и кнопку «Утвердить» в :loading) ещё VOTE_SETTLE_MS
(3с), за это время голос успевает обработаться. На ошибке загрузка снимается
сразу. Заодно ручное мутирование processingDecisions + setTimeout-хак заменены
аккуратным реактивным хелпером setProcessing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Повестка совета показывает все НЕутверждённые вопросы. Голос «за»/«против» не
должен убирать пункт — он остаётся неутверждённым, лишь помечается отметкой
голоса. Убрал ошибочное actedDecisionIds.add из onVoteFor/onVoteAgainst
(оставлен только тихий рефетч). Скрытие через actedDecisionIds оставлено только
в onAuthorizeDecision — там пункт исполняется и должен уйти из повестки без
возврата от отстающего поллинга.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Две проблемы при голосовании/утверждении в повестке совета:
1. Моргание всей страницы: QuestionsTable подменял список тремя
скелетонами при любом loading=true, а пост-экшен loadDecisions вызывался
без hidden → loading=true → список «моргал» в скелетоны и обратно.
Скелетоны теперь показываются только на первой загрузке (loading &&
!decisions.length), а пост-экшен рефетчи переведены в тихий режим (hidden).
2. «Исчез → вернулся → исчез»: повестка на бэкенде уже не отдаёт пункт, по
которому проголосовал/утвердил, но данные из блокчейна доходят с задержкой,
и поллинг успевал вернуть отработанный пункт. Добавлен локальный набор
actedDecisionIds — проголосованный/утверждённый пункт прячется сразу и не
возвращается, что бы ни подтянул отстающий рефетч. Просто, без оптимистичных
merge-оверлеев.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Две независимые проблемы при выдаче разрешения на уведомления:
1. Фриз приложения: shouldShowDialog был computed с побочным эффектом —
внутри геттера вызывался updateSupport(), присваивавший store.support
новый объект на каждое чтение. После выдачи разрешения это давало каскад
инвалидаций/ре-рендеров, забивавший главный поток (роутер менял URL, DOM
«застывал»). Геттер сделан чистым, updateSupport() вынесен в showDialog().
2. Красная ошибка «Service Worker не готов в течение 5 секунд»: в dev без PWA
SW намеренно не регистрируется, поэтому navigator.serviceWorker.ready
никогда не резолвится. Теперь сначала проверяем getRegistrations() — при
отсутствии SW выходим сразу и тихо (warn вместо error), а subscribe()
возвращает false без FailAlert. Push при недоступности SW полностью
изолирован и не влияет на работу приложения.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Кнопки взноса и возврата на столе пайщика недоступны, пока пайщик не
подписал главное соглашение цифрового кошелька (type='wallet') — как и
сама карточка кошелька, которая не появляется без подписанного соглашения.
Геттер isWalletAgreementSigned в сторе Wallet читает тот же список
соглашений пайщика, что и RequireAgreements.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
«(опционально)» в заголовок + пояснение в описании: если есть пайщики —
импортируйте CSV, если нет — двигайтесь дальше. Чтобы шаг не воспринимался
обязательным.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
readonly outlined q-input рендерится с dashed-рамкой (дефолт Quasar) и
обрезал значение (…UJR4) — выпадало из канона и выглядело плоско.
Сделал ключ surface-2 панелью: eyebrow-лейбл + copy-иконка в шапке,
mono-значение целиком (word-break) ниже. Даёт вес и убирает «ёлочку».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Прошлый вариант разъезжался: «Скопировать» висел сиротой справа, провалы
между блоками, узкая потерянная кнопка, карточка 560px — разрежено.
Переделал по канону:
- копирование ключа — иконкой content_copy в append самого поля (+tooltip)
- кнопка «Установить ключ» — full-width (block), как «Войти» в LoginForm
- ширина карточки 480 как у остальных auth-карточек
- чекбокс слева, плотный ритм без лишних margin (reserve-hint-space)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Голый «Потребительский Кооператив» в примере провоцировал вводить просто
ОПФ без расширения. Дал полный пример «...Социального Комплекса» во всех
трёх падежах — чтобы вводили расширенную ОПФ+, а не базовую форму.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Это не переменные, а постоянные параметры, которыми настраивается фабрика
документов кооператива. Переименовал label и intro шага.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Микроотступ стал лишним и снова раздвигал поля слишком широко — откатываю.
Разделение полей обеспечивает сам reserve-hint-space, дополнительный
глобальный отступ не нужен.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Постоянный hint под полем занимал место и отвлекал. Перенёс примеры
наименований ОПФ+ в placeholder (видны только в пустом поле), убрал hint.
Добавил reserve-hint-space всем q-input формы — строка под ошибку
резервируется без текста, валидация не вызывает layout shift.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Без зазора текст hint/error верхнего поля почти касался границы нижнего.
Вернул маленький отступ, но системно и один раз — глобально в quasar-canon:
margin-bottom: var(--p-1) только полям .q-field--with-bottom (те, что
резервируют нижнюю строку). Работает везде (BaseForm, формы установки,
регистрация), в стеках не складывается в дыру, инлайновые поля не трогает.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
reserve-hint-space у q-input/BaseInput уже даёт ~24px снизу под error/hint;
дополнительный gap в стеках полей складывался с ним → избыточное расстояние.
Убрал gap (канон BaseForm__body):
- CreateOrganizationDataForm / IndividualDataForm (.user-data-stack)
- SetVariablesForm (.vars-section__fields)
- RequestKeyForm (.request-key)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Канон-плотность полей (dense) была только в BaseInput; общие формы
CreateOrganizationDataForm и IndividualDataForm + поля шагов установки
рендерили крупные не-dense q-input. Добавил dense:
- CreateOrganizationDataForm (22 поля) — данные организации
- IndividualDataForm (6 полей) — члены совета
- email-инпуты SetInitForm/SetSovietForm и поля SetVariablesForm
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- InstallCooperativePage: VerticalStepper вместо q-stepper, canon-панель
с accent-стрипом, экран завершения в каноне (без градиентов/shimmer)
- RequestKeyForm: BaseInput (mono) + BaseButton
- SetInitForm: canon info-нотки, кнопки Назад/Далее на BaseButton
- SetSovietForm: canon-карточки членов совета вместо q-card/q-badge
- SetVariablesForm: canon-секции, q-input outlined с правилами, BaseButton
- Invite: осмысленное тело AuthCard при отсутствии токена приглашения
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
.extension-page уже внутри .catalog-shell__content с padding var(--p-6);
собственный padding давал двойной зазор от шапки до кнопки «Назад».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Весь контент страницы расширения обёрнут в канон-панель (surface+border),
больше не лежит на голом фоне
- В режиме настроек добавлена кнопка «Отменить» (CancelButton) рядом с
«Сохранить» — явный выход без сохранения; кнопка «Назад» страницы тоже работает
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Кнопка «Назад» убрана из шапки (useBackButton снят), добавлена канон-кнопка
под шапкой на самой странице
- Логотип AutoAvatar центрирован в колонке
- DesktopsList: центрированный inset-блок (выделяется), а не плоский список
- ext-actions toggle: «включено» и «удалить» снова в одной строке
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Триггер ринг-анимации перенесён с hover самого SVG на hover карточки
(.app-card:hover :deep(.ring-seg)).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AutoAvatar получил режим animated: инлайн-SVG вместо <img>, каждый
сегмент-дуга обёрнут в .ring-seg. По hover логотипа сегменты на ~1.3с
расходятся — прямые и зеркальные (matrix-flip) от одного поворота идут
в разные стороны, со стаггером по индексу. CoopCard не затронут (img/blob).
Уважает prefers-reduced-motion.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Статус идёт сразу под заголовком (динамично) — без дыры под однострочными
названиями. Обрезку в 2 строки оставили, чтобы длинный заголовок не ломал сетку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заголовок резервирует 2 строки (clamp+min-height), аватар по верху —
статус и описание на всех карточках начинаются на одной линии, не прыгают.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Верхний ряд: логотип + (заголовок, под ним статус); описание во всю
ширину ниже; футер «Подробнее» прижат к низу для выравнивания карточек.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Палитра колец → мягкие приглушённые тона + CSS saturate(.5)/opacity .85,
чтобы знак читался как тихая текстура, а не неоновое пятно.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- AutoAvatar параметризован: props size/radius/background/ringColor
с обратно-совместимыми дефолтами (CoopCard не затронут)
- ExtensionCard: буква-монограмма заменена на уникальный rings-логотип,
seed = имя расширения; цвет кольца детерминирован по seed из палитры
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- PaymentProviderForm: убраны вложенные q-gutter (кривой левый отступ
селекта), standout=bg-teal → outlined, text-grey-7 → токен; чистый
канон-столбец (селект max-width 480, hint ink-2, действия в ряд, no-caps).
- ChangeContacts: текст уточнён — контакты видны всем (раздел «Контакты
кооператива» пайщикам + подвал сайта незарегистрированным), не только
незарегистрированным.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Кооперативные участки, Регистрационные взносы, Ключ кооператива,
Провайдер платежей, Контакты кооператива:
- убран дублирующий заголовок страницы (есть крошка в шапке);
- описание перенесено с голого фона на канон-поверхность .banner;
- page-shell/hero/surface-card с хардкод-px → канон-отступы и токены;
- q-input/q-select standout=bg-teal → outlined color=primary.
Также удалён лишний хвост текста на Стартовых страницах.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заголовок страницы уже показан крошкой в шапке — повтор h2 убран.
Пояснительный текст вынесен с голого фона на канон-поверхность (.banner
с инфо-иконкой). ApprovalsPage не трогаем — там заголовка страницы нет.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ApprovalsPage: канон-отступы страницы, фильтр-селект teal standout → outlined.
- SystemSettingsPage: hero-card/surface-card с хардкод-px → канон page-head
(h2 + sub на токенах) + q-card(flat); канон-отступы.
- MembersPage: тот же канон-паттерн вместо hero-card с хардкодами.
- DefaultPagesForm: 4× q-select standout=bg-teal → outlined color=primary.
Виджеты таблиц/состава совета без экзотики — канон-тема через quasar-canon.css.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Шаблон двухкорневой (кнопка + Teleport затемнения), class из родителя
(LeftDrawerMenu) не наследовался автоматически. inheritAttrs:false +
v-bind=$attrs на корневую кнопку — атрибуты направлены явно.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Подписи секций «Вопрос на повестке»/«Проект решения» переведены в
надстрочные метки (uppercase, трекинг, ink-3) — отдельный ярус ниже
заголовка окна, чтобы не казаться конкурирующим заголовком рядом с
крупным заголовком самого документа. Текст вопроса — читаемый body.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ConnectPage: добавлены канон-отступы страницы (var(--p-6)/var(--p-4)) —
класс .padding в проекте не определён, контент липнул к краям.
- OnboardingStepsCard: убран q-card (двойной паддинг), текст-интро
растянут на полную ширину (снят max-width 70ch), вокруг кнопки шага
убрана лишняя рамка (.step__content → .step__action, только отступ).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- OnboardingStepsCard: под заголовком «Адаптируйте кооператив…» добавлен
поясняющий текст (что происходит дальше: решения совета в электронной
форме, вступление в «Восход», импорт пайщиков, общее собрание) + плашка
срока адаптации с отсчётом.
- Чек-лист шагов переведён на канон-вертикальный степпер (.stepper--v):
пройденные — галочка/заливка primary, текущий — подсветка номера,
действия — канон BaseButton; agenda/import/meet типы сохранены.
- Диалог «Предложение повестки» (объявить собрание совета) переведён
с ModalBase на канон BaseDialog + BaseButton.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CommonHeader: крошка строится из route.matched (путь внутри стола),
напр. «Отчётность › Календарь»; корень стола не выводится; плоские
страницы дают одну крошку как прежде; pageTitleOverride сохранён.
- CoopWalletsPage: переводы временно скрыты под флагом transferEnabled=false
(открывались с L3-кошельков пайщиков; вернём для кооп-кошельков позже).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- editor-container/action-panel/backdrop/validation-badge/mark-hint: hex и rgba → --p-* токены
- text-grey-8 → t-muted; box-shadow/overlay через токены с fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SecondLevelTabs на глобальных .tabbar/.tab — sub-навигация между топбаром и контентом
- WalletsPage/DocumentsPage shell-страницы: вкладки в .tabbar вместо RouteMenuButton в топбаре
- TransferWalletsButton: канон header-кнопка (q-btn primary)
- WalletTransferDialog: на BaseDialog + BaseButton, токены вместо rgba
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ZodForm: убран standout="bg-teal text-white" с динамического поля —
QInput/QSelect теперь канон (outlined+dense+color=primary+reserve-hint-space),
как BaseInput. SCSS на канон-токены (вместо неопределённого --q-primary-rgb).
ExtensionSettings: снят заголовок «Настройки» внутри карточки — он дублировал
«Настройки аренды» в шапке.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Канон-инпут вместо q-input: BaseInput расширен поддержкой type="date"
и clearable (+ emit clear) — это нужно для фильтра по датам. Фильтры
в ExtensionLogsList переведены на BaseInput.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ExtensionSettings: EmptyState + BaseCard вокруг ZodForm вместо самописных
header/empty-state. ExtensionLogsList (используется только powerup):
q-table → список канон-карточек с фильтром по датам, скелетоном,
«Загрузить ещё» и EmptyState; слот #log-item и API сохранены.
Канон-падинги страниц настроек/лога аренды.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Монитор ресурсов: баннер-предупреждение на всю ширину сверху, ряд
«Как это работает» (2/3) + кошелёк AXON (1/3), ряд CPU/NET/RAM.
Убран 3D-флип карт (он же «прыгал») — детали раскрываются inline.
Все виджеты/страницы/лог сведены к канон-токенам surface/line/ink.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Реестр платежей: кнопки действий больше не block (не заполняют ячейку
от левого края, не липнут к бейджу статуса). Стопка inline-flex,
равная ширина кнопок (align-items:stretch), прижата к правому краю
колонки (col-action 168px, text-align:right) — слева остаётся воздух.
- Реестр документов: col-date 116→156px — дата «26.05.2026 08:25» больше
не налезает на заголовок документа; min-width 720→780.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Bank.vue: панель платежа max-width 380px по центру — длинное «Назначение»
больше не раздувает диалог «Совершите взнос» до max-content; значение
переносится по строкам (overflow-wrap: anywhere).
- Кнопки статуса платежа SetOrderPaid/RefundedStatusButton → BaseButton
(primary/danger, block), подписи «Подтвердить»/«Отклонить»; диалог
вынесен из-под кнопки.
- Реестр платежей: кнопки действий друг под другом (.cell-actions),
колонка действий 156px, дата без переноса (.col-date 132px), min-width
таблицы уменьшен.
- Реестр документов: сужены колонки подписей (220→150), действий
(110→56), id/дата; min-width 880→720 — освобождено место под
наименование, таблица не вылазит за страницу.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Таблицы документов и платежей уже на канон-токенах и самообрамлены
(.table-wrap). Не хватало полей страницы как в реестре пайщиков:
- PaymentsPage рендерил виджет голым, впритык к краям → q-page + 24/16px.
- ListOfDocumentsPage: q-page.padding (16px) → канон-24px.
- Убрана лишняя .row.justify-center обёртка в ListOfDocumentsWidget
(.col-12 и так во всю ширину).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AddUserDialog перестроен в канон-визард по образцу CreateMeetForm:
шапка-bar (surface+line) вместо bg-gradient-dark, VerticalStepper из трёх
шагов — Электронная почта → Тип и данные → Вступительный взнос, BaseButton
в подвале, outlined-инпуты. UserDataForm переиспользован целиком (общий,
не тронут). Опция начисления взноса — канон-блок вместо q-item/q-card.
ParticipantsImportDialog: добавлено описание-интро (раньше отсутствовало),
ModalBase/bg-gradient-dark заменён на канон-bar, выбор типа аккаунтов через
BaseRadioCard, dropzone на токенах вместо хардкод-цветов, секции и действия
в канон-обёртках, BaseButton вместо цветных q-btn. Логика парсинга/импорта
и таблицы превью/результатов сохранены.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Страница получила канон-отступ (--p-6 / --p-4 на мобилке), таблица обёрнута
в обрамлённую surface-карточку (--p-line, --p-r-lg) вместо edge-to-edge.
Развёрнутые данные пайщика — спокойная вложенная панель на --p-surface-2
с отступами --p-5/--p-6, читаемой шириной формы (640px) и вертикальным
ритмом между полями. Снят full-height у таблицы (мешал внутри карточки).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Развёрнутая строка пайщика теперь показывает только его данные;
документы пайщика живут в отдельном «Реестре документов» и здесь не дублируются.
ParticipantDetails упрощён (без q-tabs/ListOfDocumentsWidget), tab-логика
вычищена из таблицы и страницы. Мобильная ParticipantCard переведена на канон-токены
(убраны хардкод-цвета, CardStyles и q-badge статуса → disabled-checkbox).
Таблица сохранена, сортировка по дате вступления (DESC) без изменений.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Убран EntityIdBadge. Зелёная плашка-аватар слева теперь несёт сам номер вопроса
(он же Decision ID): клик по плашке копирует id (тултип «Скопировать №»), hover —
инверсия в сплошной teal. Заголовок снова просто текст вопроса.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EntityIdBadge перенесён из отдельной строки под ФИО в начало строки заголовка:
[#id] → сразу текст вопроса. Inline, vertical-align middle. Раньше висел снизу
и пусто растягивал верхнюю строку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EntityIdBadge с id вопроса под заголовком/ФИО: отображает #<id>, по клику копирует
чистый id (copy-on-click). Канон-компонент, клик не раскрывает карточку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
«Истекает через месяц» перенесён из строки под заголовком в постоянную нижнюю
полоску (footer): срок слева, «Утвердить» справа (только председателю). У обычного
пайщика — только срок, узкая панелька.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Срок теперь подписан «Истекает через месяц» (было просто «через месяц» — неясно).
Удалён статус-чип «Вы за/против» — направление голоса уже видно по подсветке кнопок,
бейдж был избыточным. Убраны связанные computeds и стили.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Кнопки голосования прижаты к правому краю строки. «Утвердить» вынесена вниз
отдельной строкой-footer (справа, hairline сверху) — больше не теснит данные.
Срок «через месяц» и статус-чип «Вы за» перенесены влево под заголовок/ФИО.
Клик по строке раскрывает документ, по кнопкам голосования и footer — нет.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Карточка вопроса перестроена в единую горизонтальную строку: иконка + вопрос/ФИО
слева (flex), компактный блок органов управления (голосование + «Утвердить»),
срок + статус-чип и шеврон — справа, друг рядом с другом. Убрана центрированная
vote-зона с пустотой по бокам. Клик по строке раскрывает документ, по органам
управления (@click.stop) — нет. Подсказка «Утвердить» возвращена в tooltip.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Шапка карточки (вопрос/ФИО/срок/статус) кликабельна целиком и раскрывает документ,
справа шеврон-индикатор. Органы управления голосованием — отдельная зона ниже шапки
(сиблинг, не аккордеон): клик по ним голосует/утверждает и документ не раскрывает.
Убрана отдельная кнопка «Документ» — раскрытие теперь по клику на шапку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
QuestionCard: VotingButtons + «Утвердить» вынесены на верхний уровень карточки —
голосовать можно без раскрытия; раскрытие («Документ») открывает только содержимое
документа. Истечение срока остаётся в шапке.
VotingButtons: вернул чек-индикатор «принято советом» (закрашивается при принятии)
вместо иконки verified.
Header-CTA совета (Предложить/Добавить/Импорт): убран push (q-btn--push не попадал
под канон-правило заливки primary → белый шрифт на нетиловом фоне), подписи с заглавной.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
QuestionsTable: q-table → карточный список (скелетоны + EmptyState).
QuestionCard: канон-поверхность, BaseButton «Утвердить», токен-чип статуса
вместо q-badge, локальное состояние раскрытия, без @import CardStyles и хардкод-hex.
VotingButtons: токены pos/neg вместо .text-red/.text-green/#666.
Страница: канон-padding вместо q-card flat.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Названия столов в реестре workspace'ов заведены вразнобой («Стол
благороста», «Стол вычислительных ресурсов» vs «Стол Совета»).
Добавил text-transform: capitalize на подписи в WorkspaceSwitcher
(заголовок текущего стола + пункты выпадающего меню) и WorkspaceMenu
(карусель + диалог выбора) — первая буква каждого слова заглавная,
одинаково для всех и для будущих столов, без правки строк-источников.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
По фидбеку переработана композиция:
- убраны заголовки секций «Контакты»/«Руководство» и per-строчные
иконки телефона/почты/адреса (из-за них значения «прыгали» вправо
относительно ИНН/ОГРН);
- ИНН и ОГРН теперь в одну строку (адаптивная сетка полей);
- председатель поднят наверх к реквизитам, поле названо просто
«Председатель совета»;
- контакты — те же поля, телефон/email как ссылки (tel/mailto), всё на
единой левой кромке; одна тонкая линия делит реквизиты и контакты.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Реквизиты/Руководство больше не разносят подпись и значение к
противоположным краям (на полной ширине это давало пустой провал и
оторванные значения). Все секции теперь как ContactSheet: подпись
сверху, значение под ней, слева, hairline между строками — один ритм
по всей карточке.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ContactsPage: три цветные ColorCard (orange/indigo/teal/blue) заменены
на единую спокойную canon-поверхность с секциями через hairline
(Реквизиты / Контакты / Руководство). Контакты — через canon
ContactSheet (копирование, mailto/tel). Заголовок и строки на токенах
(--p-ink/-2/-3, --p-surface/--p-line/--p-r-lg), полная ширина (padding
24/16px). Убраны rgba-фоны и .q-dark-хаки.
- Удалён неиспользуемый виджет MeetQuorumIndicator (нет импортов;
явка/кворум живут в MeetInfoCard).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ListOfMeetsPage / MeetDetailsPage: убран max-width:960px center — контент
во всю ширину с canon-паддингом (24px/16px), как документы/платежи.
- MeetInfoCard: переписан с токсичного teal на canon — единая calm-поверхность
с тремя секциями через hairline (Даты / Ведущие / Явка и кворум) вместо
трёх вложенных цветных карточек; заголовки ink, проценты ink (не teal);
дубль заголовка «Общее собрание № N» убран (он в шапке). Убраны
var(--q-primary), color-mix, .body--dark, хардкод-rgba.
- MeetDetailsInfo: контейнер с .card-container → canon.
- MeetDetailsActions: q-btn color=primary → canon BaseButton.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CreateMeetForm: повестка на шаге «Проверка» — аккуратные canon-карточки
(Вопрос / Проект решения / Приложения) вместо скомканного списка.
- MeetCompactCard: переписана на canon — нейтральный hover (без
токсичного teal-свечения), ink-заголовок, canon-плитки/иконка;
убраны @extend .card-container, var(--q-primary), .body--dark, color-mix.
- MeetStatusBanner: нейтральный canon-контейнер (surface-2 + line),
цвет статуса несёт иконка; убраны хардкод-rgba и .body--dark.
- MeetCardsList: empty-state на canon EmptyState, skeleton на .skel.
- Детали собрания: кнопка «Назад» убрана из топбара (снят useBackButton),
добавлен canon back-link под шапкой слева; название собрания выводится
в заголовок шапки через новый desktopStore.pageTitleOverride
(приоритет над route.meta.title; транзиентно, чистится при уходе).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Добавлен canon-компонент TableSkeleton (shared/ui/base) — повторяет
структуру .table-wrap/.table с реальными заголовками и мерцающими
плейсхолдерами (.skel) в ячейках. Каркас не дёргается при подгрузке
данных (calm-data, UX-DR2/UX-DR23). Заменил перекрывающий q-spinner
в DocumentsTable и ListOfPaymentsWidget.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- DocumentsTable: наименование берём из meta.title (чистое, без даты/.pdf
суффикса full_title); отдельная колонка Дата (meta.created_at) с сортировкой
по block_num, по умолчанию свежие сверху; колонка Документ — только заголовок.
- ID — копируемый EntityIdBadge (показывает короткий хеш, копирует полный
doc_hash по клику + иконка-affordance).
- Подписи — отдельные BaseBadge на подписанта (новый helper
getSignersListFromDocumentPackage возвращает массив).
- EntityIdBadge канонизирован: токены вместо rgba/--q-accent/.q-dark; добавлен
copyValue (показать одно — скопировать другое).
- SearchHeaderAction: на мобильном — минимальная round-dense иконка без подписи
«Поиск» (label только на десктопе).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Переделка после ревью: q-table давал не-канон вид, дёрганье при раскрытии
(virtual-scroll пересчитывал размеры + colspan не совпадал с числом колонок)
и уродливые мобильные карточки в grid-режиме.
- Статическая канон-таблица .table-wrap/.table (бордер+радиус+surface как
карточка), table-layout:fixed → колонки не разъезжаются при раскрытии.
- Раскрытие строки — CSS-only (expand-row td colspan по числу колонок),
без virtual-scroll → нет дёрганья шапки/колонок.
- Пагинация load-more (.table-foot + BaseButton «Загрузить ещё» + «1–N из M»)
вместо infinite virtual-scroll.
- Мобильный: горизонтальный скролл таблицы (.table-scroll) вместо grid-карточек
PaymentCard/DocumentCard — карточки удалены.
- Статусы платежей — BaseBadge (pos/warn/neg/info/neutral); направление —
иконка + цвет --p-pos/--p-neg; хеш документа — mono.
- Действия платежей (SetOrderPaid/Refunded) скрыты на столе пайщика
(hideActions=true) — они в реестре платежей; download документов сохранён.
- EmptyState + спиннер первой загрузки.
Виджеты общие с админ-контуром: load-more и горизонтальный скролл там тоже
уместнее jittery virtual-scroll.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Когда страница не телепортирует действия, .topbar__actions скрывается
display:none через :has(пустой host), но adjacent-селектор
.topbar__actions + .topbar__right всё равно матчился и обнулял margin-left
правой группы — она уезжала влево к крошке. Возвращаем auto в :has-правиле
(его специфичность выше). Также убрал лид-надпись на странице реквизитов.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ProfilePage (Удостоверение): IdentityPanel-шапка + BaseCard-секции (Учётная
запись/Личные данные/Документы и реквизиты) на DataRow; убран CardStyles,
хардкод rgba и .q-dark.
- PaymentMethods widget (Реквизиты): BaseCard на метод + DataRow + EmptyState
вместо .info-label/.info-value и хардкод-цветов.
- PaymentMethodsPage: кнопка добавления реквизитов переведена с useHeaderActions
store на canon Teleport (#header-actions-host), micro на мобильном.
- AddPaymentButton: триггер под canon micro-паттерн (как Deposit/WithdrawButton).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Симлинк node_modules -> /home/admin/mono-ai-2/node_modules был случайно
закоммичен и ломал pnpm install (ENOTDIR) на любой машине без этого пути.
Правило .gitignore 'node_modules/' (со слешем) ловит только каталог, не
симлинк-файл — добавлено правило 'node_modules' без слеша.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Крошка: в #crumb рядом с BackButton выводится route.meta.title текущей
страницы (тот же, что подсвечен в меню) — именно для неё действия и
сдвинуты вправо. Длинное название обрезается ellipsis в .topbar__crumb b.
ModalBase: q-bar получил авто-высоту, заголовок переносится (white-space
normal + overflow-wrap), крестик прижат к верху — на узком экране титул
больше не обрезается сверху.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: полноразмерные DepositButton/WithdrawButton в узкой мобильной шапке
раздувались — текст переносился в 2 строки, кнопки вылезали за высоту
topbar. micro-вариант (иконка + tooltip, flat/dense) и предназначен
для слота шапки.
What: Teleport-кнопки получают :micro='isMobile' (useWindowSize, <768px).
Мобильный — компактные иконки; десктоп — полные кнопки с подписью.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Кошельки (по фидбэку — переносы выглядят плохо):
- .wallet-programs из grid (repeat auto-fill minmax) → flex-column: карточки
идут списком во всю ширину страницы.
- canon .wallet__title/__sub: возвращён nowrap + ellipsis (откат wrap-фикса);
на широкой строке текст почти всегда влезает, иначе — ellipsis.
- WalletCard + минимум-карточка: нативный tooltip `title` — при наведении
виден полный текст. Убран .wallet--row reset (базовый снова nowrap).
Действия шапки (кнопки взноса/возврата пропали; нужна новая механика):
- Новый canon-механизм: страница телепортирует свои действия в шапку через
<Teleport to="#header-actions-host">. Host — постоянный span с
display:contents в #actions слоте CommonHeader (при loggedIn).
- :has()-правило прячет .topbar__actions, когда внутри только пустой host
(нет ни store-кнопок, ни телепорта) — чтобы не было пустого разделителя.
- WalletPage переведён на Teleport (DepositButton/WithdrawButton),
useHeaderActions store-механизм убран со страницы.
- Старый useHeaderActions оставлен для прочих 10 страниц — мигрируем
и удалим отдельно. DepositButton сам скрыт, пока пайщик не принят
(status !== 'active') — это защита, не баг.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: на странице без логина логотип был <img :src='logo.svg'> — img рендерит
SVG в изоляции, не наследует currentColor, поэтому показывался чёрно-белым
и не реагировал на смену темы. В личном кабинете (WorkspaceSwitcher)
тот же logo.svg рендерится inline через v-html в зелёном квадрате
и наследует color (logo.svg на fill:currentColor) — «зелёненький
на зелёном фоне», одинаковый в обеих темах.
What: CommonHeader brand-slot переведён на тот же приём —
`logo.svg?raw` + v-html внутри .app-q-header__logo
(background var(--p-primary-soft), color var(--p-primary), 28px квадрат,
16px svg). Переключение темы больше не требуется — зелёный константен.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: предыдущий фикс `overflow-wrap: anywhere; word-break: break-word`
ломал слова в любом месте даже когда колонка достаточно широкая —
«Минимальный неснижаемый остаток» рендерился по одному слову на строку,
несмотря на ~480px доступной ширины. Реально нужен только wrap по пробелам;
agressive break-word оправдан только для URL-подобных нерасчленяемых строк.
What: убраны `overflow-wrap` и `word-break` из .wallet__title/__sub —
браузер делает естественный wrap по пробелам, длинные заголовки переносятся
только когда не помещаются. `.wallet--row` reset для compact-варианта
оставлен — там по-прежнему single-line+ellipsis.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: canon-стиль .wallet__title и .wallet__sub был с
`white-space: nowrap; overflow: hidden; text-overflow: ellipsis;` — на full-
варианте это резало длинные подписи («Минимальны…», «Возвращается п…»)
даже на просторных экранах, потому что grid-колонка `.wallet__main` сжимается
ради `.wallet__amount` справа. Эта обрезка будет всплывать на любой
длинной строке (метки программ, статусы пайщика, длинные subtitle).
Compact-вариант `.wallet--row` (слот шапки) должен остаться одной строкой.
What:
- .wallet__title/__sub: убран nowrap/ellipsis; добавлено
`overflow-wrap: anywhere; word-break: break-word; hyphens: auto` (для title)
и `overflow-wrap: anywhere; word-break: break-word` (для sub) — длинные
заголовки переносятся на 2+ строки.
- .wallet--row: явно возвращает nowrap+ellipsis (с reset word-break/
overflow-wrap), чтобы compact-ряды в шапке оставались строго одной строкой.
- WalletProgramWidget: карточка минимального неснижаемого остатка
перемещена в начало grid'а (была после программ) — это базовая защита
средств пайщика, логичнее видеть её первой.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why:
- Заголовок «Кошелёк» для program='wallet' путал — в столе пайщика этот
кошелёк семантически главный, и в любых других местах canon (MicroWallet,
WalletCardMini, _dev/ui) он тоже должен называться полно — «Главный
кошелёк». Лучше один canon-default, чем локальный override в каждом
потребителе. Согласовано — переименование canon DEFAULT_TITLES.
- Минимальный неснижаемый остаток — НЕ баланс кошелька, а самостоятельная
сущность пайщика (паевой взнос, возвращается при выходе). Пристегивать
его DataRow-строкой под grid'ом — нелогично, как было и раньше в legacy.
Правильнее — отдельной карточкой в той же сетке кошельков.
What:
- WalletCard.vue: DEFAULT_TITLES.wallet 'Кошелёк' → 'Главный кошелёк'.
Глобально для всех потребителей canon-компонента.
- WalletProgramWidget.vue: убран TITLE_OVERRIDE и DataRow-блок минимального
остатка. Карточка остатка теперь рендерится в общем .wallet-programs
grid'е canon-разметкой `.wallet` с иконкой `savings`, заголовком
«Минимальный неснижаемый остаток», подзаголовком «Возвращается при выходе
из кооператива» и зарезервированной суммой. Нейтральная подсветка иконки
через `.wallet--minimum { --prog-bg, --prog-fg }`, чтобы визуально
отличалась от программ, но встала в общую сетку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: на старом WalletWidget пайщик видел свой минимальный неснижаемый остаток
(паевой взнос, возвращается при выходе из кооператива) — самостоятельная
сущность пайщика, не баланс кошелька. При переходе на canon я её упустил.
Канон-default WalletCard 'wallet' = «Кошелёк» — для стола пайщика этот
кошелёк семантически является главным (свободный остаток ЦК), поэтому
локально перекрываем заголовок на «Главный кошелёк».
What:
- TITLE_OVERRIDE['wallet'] = 'Главный кошелёк' — локальное перекрытие
заголовка только в этом widget'е, canon DEFAULT_TITLES не трогаем
(другие потребители WalletCard могут использовать общий «Кошелёк»).
- DataRow «Минимальный неснижаемый остаток» под grid'ом программ
(только когда session.participantAccount.minimum_amount > 0)
с hint «Возвращается пайщику при выходе из кооператива».
- Источник остатка — session.participantAccount?.minimum_amount,
как и в legacy WalletWidget.
Note: locked-line уже рендерится самим canon WalletCard, когда
locked-balance > 0 (logic: hasBlocked ? blocked.amount : undefined
в WalletProgramWidget). Отдельная разметка под «Заблокировано» не нужна.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: type predicate `e is CanonProgramEntry` не сходился — optional `locked?: string`
в interface vs required `locked: string | undefined` в литерале map'а. TS считает
эти типы несовместимыми для predicate, хотя они эквивалентны при присваивании.
What: переход на `flatMap<CanonProgramEntry>` с возвратом `[]` для исключаемых
программ. Predicate не нужен, generic flatMap даёт точный тип результата.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: первая страница стола пайщика (default route 'wallet') использовала legacy
обвязку — CardStyles import, scoped SCSS с .body--dark и hardcoded rgba цветами,
ColorCard с произвольным цветом по индексу. Канон уже знает программы платформы
(blagorost/wallet/generator) через WalletCard + токены --prog-*; на нём и строим.
What:
- WalletPage.vue: убран import 'src/shared/ui/CardStyles', scoped SCSS на
токенах --p-6/--p-4; вырезана легаси-карточка «Минимальный остаток»
(она не отображалась — не было разметки в template). useHeaderActions
для Deposit/Withdraw оставлен — канон поддерживает actions через #actions slot.
- WalletProgramWidget.vue: переписан на canon WalletCard + EmptyState.
Фильтр на канон-набор программ через ZEUS_TO_CANON
(MAIN→wallet, BLAGOROST→blagorost, GENERATOR→generator);
MARKETPLACE и прочие исключены — это не из основной тройки платформы
и должны рендериться отдельным виджетом.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: на главную без логина нужно показывать бренд кооператива (логотип + имя)
рядом с глобальными действиями шапки; раньше выводилось только текстовое
название через title-проп без логотипа.
What:
- AppHeader.vue: опциональный slot #brand перед .topbar__crumb;
hasBrand computed по slots.brand. Когда slot заполнен — крошка не рендерится.
- components.css: стили .topbar__brand (desktop + .topbar--mobile вариант)
с canon-токенами (--p-fs-body, --p-ink, --p-fs-meta).
- CommonHeader.vue: на !loggedIn заполняет #brand src/assets/logo.svg
+ <b>{coopTitle}</b>; передача title-пропа убрана.
- default.vue: .fixed-top-right { top: 51px } → top: var(--p-topbar-h)
(canon 56px) — выравнивание FAB под точную высоту шапки.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Существующая модель cmdk в проекте (entities/CmdkMenu/model/store.ts) —
иерархия рабочих столов и их страниц. Активный стол sticky сверху с
бейджем «Активный», без запроса показывается иерархия (стол + indented
страницы), с запросом — плоский список со столом-префиксом у каждой
страницы; стол отдельной строкой появляется только если запрос явно
начинается с его имени или содержит «стол»/«workspace».
Переписал canon CommandPalette под эту модель:
- Props: `workspaces: CommandPaletteWorkspace[]` вместо
`commands: CommandItem[]`. Каждый workspace = `{ name, title, icon,
isActive?, pages: CommandPalettePage[] }`. Page = `{ name, title,
icon?, shortcut? }`.
- Emits: `select-workspace(name)`, `select-page(workspaceName, pageName)`
— props-only, навигация и filtering по ролям/conditions остаются
заботой connected-обёртки (миграция legacy CmdkMenu — отдельная story).
- Sticky-баннер активного стола, plus accent-soft фон + outline-обводка
на selected, ↑↓ работает плоско поверх иерархии (стол → страницы → стол
→ страницы).
Mock-data в /_dev/ui/index.vue обновлён: три стола (Председатель/Пайщик/
Отчётность) со своими страницами вместо плоского списка команд.
Старый widgets/Desktop/CmdkMenu пока живёт параллельно — переключим в
ходе миграции Wave 3.
Реализованы три props-only доменных компонента из E11:
- NotificationCenter — panel-content для popover в шапке: группировка
notifications по category (system/financial/voting/message), unread-bullet
через BaseBadge, кнопка «Прочитать все», EmptyState и «Показать все».
Relative-date форматирование с русским склонением.
- CommandPalette — ⌘K/Ctrl+K с fuzzy-поиском, секции recent/pages/actions,
↑↓ навигация, Enter/Esc обработка. localStorage недавних — в connected
обёртке, компонент props-only.
- DetailsDrawer — side-sheet справа 480px (override через :width), slots
default/actions/footer, Esc и backdrop close, на xs — fullscreen.
Все три зарегистрированы в boot/ui.ts и локально импортированы в /_dev/ui
с mock-data в секциях 36-38.
E11.4 RailUserCard отложен из-за конфликта имён — существующий компонент
имеет другую роль; нужно согласовать канон-нейминг.
Заменил `.full-width.text-center` + flex без выравнивания на flex-column
со `min-height: 360px` — лоадер больше не прижат к левому верху карточки.
Заодно поправил опечатку «подговка» → «Формируем документ».
Корень: я положил символ валюты в #append slot своим <span>, в обход
встроенного prop suffix. Quasar имеет правила позиционирования именно
для родного .q-field__suffix (см. revert d0a8dc0080 от 2026-05-19,
где было решено оставить Quasar дефолт — «нас устраивает»). Мой span
в append-slot не подчинялся этим правилам и сидел в произвольной
позиции относительно цифр.
Фикс:
- :suffix='symbol' — символ валюты идёт нативным механизмом Quasar.
- Удалил .amount-input__symbol класс и template #append вообще.
- Удалил override align-items: center на q-field__control/__append/
__after (тоже мешал, как было показано в d0a8dc0080).
- font-weight 500 на цифрах оставил — нормальный вес поля.
Совет на будущее: использовать встроенные q-input props (suffix,
prefix), а не #append/#prepend slots, если можно — Quasar для них
держит готовое выравнивание, проверенное пользователем.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень: font-weight: 600 + tabular-nums + Quasar dense нативный input
имеют чуть смещённую baseline относительно append-слота, где сидит
RUB. Визуально цифры лежали ниже суффикса.
Фикс:
- font-weight 600 → 500 (нормальный вес поля ввода, без bold-акцента
на цифрах).
- Явный font-size + line-height на нативном input.
- align-items: center на q-field__control / __append / __after, чтобы
суффикс и any after-слот (кнопка «макс») центрировались по высоте
входной полосы.
- align-self: center на самом __symbol — на случай если q-field__append
кто-то переопределит как stretch.
TODO на следующий подход: BaseDocument loader («Формируем документ…»)
выравнивать по центру рамки документа (сейчас стоит сверху).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
[Quasar] boot error: SyntaxError: Unexpected identifier 'as' — runtime-парсер
обрабатывает выражения в pug-template как чистый JS, без TypeScript. Касты
вида `el as HTMLInputElement | null`, `e as InputEvent`, `e as KeyboardEvent`
прямо в атрибутах `:ref` / `@input` / `@keydown` — синтаксическая ошибка во
время бутстрапа Vue, из-за которой /_dev/ui целиком не грузился.
Фикс:
- :ref='(el) => setRef(idx, el)' + функция setRef(idx, el: Element |
ComponentPublicInstance | null) с кастом в TS-скрипте.
- @input='(e) => onInput(idx, e)' + сигнатура onInput(idx, event: Event)
с внутренним кастом target.
- @keydown оставил передавать event «как есть» — KeyboardEvent — никакой
cast не нужен.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Quasar boot-файлы подхватываются только при рестарте dev-сервера. После
коммита 1af0f9c06f глобальные регистрации AmountInput/OtpInput/FilterBar/
FileUploader/VerticalStepper не подтянулись на лету — теги рендерились как
unknown components (пусто внутри секций 31–35 на /_dev/ui).
Фикс: добавил локальные импорты прямо в script setup _dev/ui/index.vue —
тот же паттерн, что у WalletCard, RailUserCard, AuthCard. Глобальная
регистрация в boot/ui.ts остаётся для боевого использования из других
страниц (после следующего рестарта dev'а).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Пять props-only доменных компонентов из shared/ui/domain/:
- AmountInput — денежный ввод с символом валюты, форматированием тысячных,
precision из marketplace asset config, кнопкой «макс» по balance, опциональной
подписью баланса. Tabular-nums, right-align, font-weight 600.
- OtpInput — 6 ячеек с автопереходом фокуса, Backspace откатывает на
предыдущую, paste 6-значного кода распределяется по ячейкам.
Регулярка /^\d$/, состояния error/disabled.
- FilterBar — search (debounce 300мс) + dropdown-фильтры + chip'ы активных
значений с remove + «сбросить всё». v-model для values, v-model:search для
поиска. Активные chip'ы рендерятся под рядом фильтров.
- FileUploader — drag&drop + клик по зоне; валидация accept/maxSize/maxFiles
→ emit error; список загруженных с иконкой типа, именем, размером,
кнопкой ×; слот progress для connected-обёртки.
- VerticalStepper — состояния pending/current/completed/error; completed
кликабельны для возврата назад (опционально); опциональные/disabled шаги;
слот active под телом текущего шага.
Все компоненты — pug, canon-токены --p-*, без store/router/api. Демо-секции
31–35 на /_dev/ui (наш стенд-витрина).
Зарегистрированы в boot/ui.ts и shared/ui/domain/index.ts. ESLint точечно
прошёл, vue-tsc не запускался полностью на desktop (запрет).
Wave 2 / E10 закрыт.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Удалены типы 'txt' и 'image' из DocumentPreviewType + соответствующие
ветки рендера (pre.document-preview__txt и img.document-preview__image)
и стили. В реальных потоках платформы документ кооператива всегда
HTML (рендерится в ShadowHtml внутри BaseDocument) или PDF — plain-
text фрагмент в моноширинном pre не используется и выглядит как
техническая ерунда. Аналогично image.
_dev: удалён previewTxtDemo (мусорный EOSIO chain id из debugging
notes → перешитый в фрагмент протокола → теперь полностью убран
по требованию пользователя). Секция 30 показывает только HTML +
loading + error состояния.
vue-tsc:
- _dev: убрал txId/explorerUrl из signatureSignedDemo — оба поля
удалены из Signature ранее (общего эксплорера нет).
- BaseDocument: canonSignatures.map — нормализую is_valid через
'?? undefined' (бэкенд может вернуть null, canon-компонент ждёт
boolean | undefined).
DocumentPreview txt demo: вместо мусорного 'EOSIO chain id …
dirty window' (мой случайный кусок из debugging notes) — фрагмент
протокола собрания пайщиков ПК «Восход».
ComplexDocument: .col-md-7 → .col-md-10. Это контейнер, в котором
лежит BaseDocument в реальных страницах документов. 7/12 = 58%
ширины было визуально 'приплюснуто'.
Реальный формат подписей кооперативного документа — это не «pending/
signed/rejected» из абстрактного SignatureCard, а IDocumentAggregate с
полями doc_hash + signatures[] (signer_certificate, public_key,
signature, is_valid). Каждая подпись разворачивается в детали.
Что сделано:
- Новый canon-компонент DocumentSignatures (story 9.5) в shared/ui/
domain — props-only, принимает уже резолвнутые signerName и hash-
совпадение, эмитит download/verify. Поверх Quasar — собственный
expand на ref<Set<number>>, чтобы стиль был полностью canon.
- BaseDocument теперь рендерит DocumentSignatures вместо своего
q-card.verify-card + q-list + q-expansion-item на teal/red badges.
Адаптер canonSignatures маппит signer_certificate → ФИО через
getNameFromCertificate, чтобы canon-компонент не знал про сертификаты.
- SignatureCard: удалены поля txId/explorerUrl и ссылка «Открыть в
explorer» — общего эксплорера в платформе нет.
Demo: секция 29 → DocumentSignatures (валидный + с битой подписью),
секция 30 → DocumentPreview (HTML заголовок сокращён, чтобы не
обрывался при узкой колонке).
4 props-only canon-компонента в src/shared/ui/domain/, регистрация
в boot/ui.ts, demo-секции 26-29 в _dev/ui:
- DocumentRow (story 9.1) — строка документа в списке: иконка типа
с tint'ом (pdf neg-soft, docx info-soft, html primary-soft), title,
status через BaseBadge, дата/автор/описание, slot actions, emit open.
- SignatureCard (story 9.3) — подписавший (Avatar+ФИО+AccountBadge),
статус (BaseChip pending/signed/rejected), для signed — хеш в моно-
блоке + ссылка на explorer, для rejected — BaseBanner с причиной.
- ActivityTimeline (story 9.4) — вертикальный таймлайн событий с
цветными иконками по типу (sign/reject/create/update/comment/
transfer), groupByDate группирует по «Сегодня/Вчера/конкретная
дата».
- DocumentPreview (story 9.2) — html (через DOMPurify), pdf через
iframe, image через img, txt через pre. Стейты loading/error.
Все props-only, темо-зависимые значения только через --p-* токены.
Раньше compact-вариант рендерился без обрамления — avatar упирался
в левый край контейнера, визуально «проваливался» из общего стека
full-карточек (Screenshot_2026-05-21_21-51-09).
Compact теперь: padding 8/12px (тоньше чем full 16px), тот же border
+ surface + radius. В одну строку avatar + ФИО + AccountBadge + status,
но визуально это всё-таки карточка, не голая строка.
Body раньше был обычным block-контейнером, а .head и .value-row —
оба display: inline-flex. Без явного block-форматирования inline-flex
элементы становятся inline и рендерились в одну строку. Визуально:
Email⭐ivanov@example.ru, Телефон+7 (903)..., Telegram@ivanov — слипшиеся.
Body теперь display: flex + flex-direction: column, gap canon — 8px
в comfortable, 4px в compact. Head/value-row переведены на обычный
display: flex (вместо inline-flex), gap внутри сохранён.
Прецедент 2026-05-21 — Screenshot_2026-05-21_21-42-16, 21-42-31.
- AccountBadge: copy-кнопка не сжимается (flex-shrink: 0), размер 20×20 + icon 14px — раньше 18×18 + 12px тонула рядом с текстом badge.
- DataRow: column-gap var(--p-3, 12px) → var(--p-5, 20px) + min-label-width 140 → 160px + padding-right на label. Между label и value был визуально слипшийся стык.
- ContactSheet: comfortable margin-top между label и value 2px → 8px (compact 4px). Раньше label «налипал» на значение.
- IdentityPanel compact: переделан в flat flex-row (avatar + ФИО + AccountBadge + status badge) — раньше grid с .body загонял имя и AccountBadge в две строки и avatar «уезжал» от имени. Spec 8.1 требует «только avatar + ФИО + EntityIdBadge в одну строку».
5 props-only canon-компонентов в src/shared/ui/domain/, регистрация
в boot/ui.ts, demo-секции 21-25 в _dev/ui:
- AccountBadge (story 8.5) — on-chain account name в mono-шрифте,
copyable + опциональный explorer link. Назван AccountBadge вместо
EntityIdBadge: имя EntityIdBadge занято под numeric ID пайщика
в capital. Decision зафиксирован в epics.md и planning-artifacts.
- DataRow (story 8.3) — `label: value` пара для реестровых карточек,
mono-режим, copyable, slot value-override, hint.
- ContactSheet (story 8.2) — email/phone/address/tg/web с автоиконками,
кликабельными mailto:/tel:/t.me ссылками, copy и verified-чек.
- IdentityPanel (story 8.1) — Avatar + ФИО + AccountBadge + статус
(active/blocked/pending) + role + actions-slot; compact/full.
- PersonCard (story 8.4) — Avatar + ФИО + role + AccountBadge +
ContactSheet + slot meta; compact/comfortable.
Все props-only: useStore/useRouter/useApi внутри запрещены. Темо-зависимые
значения только через --p-* токены, никаких body--dark селекторов.
q-checkbox по умолчанию красит .q-checkbox__bg в --q-primary, который в
dark теме равен #2dd4bf — на маленьком квадрате 18px этот яркий бирюзовый
смотрится ядовито. Переопределяем заливку filled-состояния на
--p-primary-press (#134e4a light / #14b8a6 dark) — глубже и спокойнее,
чек-символ остаётся белым.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Старое значение --p-accent (#D84315 light / #FF7043 dark) — слишком
насыщенное, выглядит ядовито рядом с тёплой палитрой ink/canvas. Меняем
на тёплую терракоту: #B85C38 (light) / #E89472 (dark) — тот же warm-tone,
но без перевозбуждения красного канала.
.agreement-link (псевдо-ссылки на просмотр документа в согласиях) была
hardcoded в #1c64f2 (синий) light / #ff9f43 (жёлто-оранжевый) dark и не
совпадала со ссылкой «Устав кооператива». Унифицируем: все ссылки внутри
согласий теперь var(--p-accent), визиt-цвет тот же — без фиолета.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseCheckbox block-вариант: убран собственный display:flex + align-items:
flex-start, который перебивал внутренний flex Quasar и сдвигал label вверх
относительно inner. Теперь Quasar сам центрирует inner на первой строке.
Добавлен padding-left: 8px на .q-checkbox__label — больше воздуха между
галочкой и текстом согласия.
ReadStatement: ссылка «Устав кооператива» — --p-primary → --p-accent
(тёплый оранжевый #D84315 / #FF7043 dark) как акцентный цвет палитры.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UserDataForm/UserDataForm.vue:
- Выбор типа аккаунта: три q-btn(glossy, height:75px) → три BaseRadioCard
с title/description; больше нет teal-«кнопок-плиток»
- Возврат к выбору типа: flat q-btn → BaseButton(ghost) с arrow_back
Sub-forms (Individual / Entrepreneur / Organization + Create*):
- q-input/q-select: standout="bg-teal text-white" → outlined color='primary'
(canon-tone, единый primary-акцент вместо устаревшего teal)
- .q-gutter-sm.q-mt-md → .user-data-stack (flex column, gap токены)
- OrganizationDataForm: кнопка «совпадает» — color='teal' → flat primary
Валидация q-form.validate() и :rules сохранены — это всё ещё q-input
с rules-массивом, только tone и контейнер переехали на canon-токены.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- BaseCheckbox: обёртка q-checkbox с canon-стилями, block-вариант для длинных
согласий (чекбокс прижат к верху, label многострочный)
- BaseRadioCard: карточка-опция с радио-индикатором справа, props title /
description / meta + slot fallback'и; используется для выбора программы
Миграции SignUp:
- ReadStatement: q-checkbox → BaseCheckbox(block); согласия в .agreements-стек
- SelectProgram: q-list+q-radio → список BaseRadioCard
- GenerateAccount: q-input → BaseInput(readonly, mono) с copy в #append;
q-checkbox → BaseCheckbox; автоселект через querySelector('input').select()
вместо ref.select() (BaseInput не пробрасывает внутренний API q-input)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Воздух над заголовком стола внутри WorkspaceSwitcher — заголовок не упирается
в верхнюю границу caption «ПК «ВОСХОД»».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- .rail__top: padding 18px 20px → 16px 8px (унификация боковых отступов)
- WorkspaceSwitcher: убраны :disabled-атрибут и hover:not(:disabled) — у пайщика, которому доступен только один стол, кнопка выглядит как обычно (без not-allowed-cursor, без приглушённого цвета). Меню q-menu просто не открывается через v-if='workspaces.length > 1'. Тихое игнорирование клика вместо явного запрета.
WorkspaceSwitcher показывал ВСЕ workspaceMenus, включая столы выше роли пользователя (например, «Стол председателя» видел рядовой пайщик), что приводило к ошибке access-denied при попытке переключения.
- Добавлена фильтрация по иерархии: chairman ⊇ member ⊇ user. Председатель видит столы любых ролей; член совета — user+member; пайщик — только user. Workspace без meta.roles (или с пустым массивом) — публичный для всех (та же логика, что в legacy WorkspaceMenu, плюс корректное наследование прав).
- Title допускает перенос на 3 строки (line-clamp 3) — «Стол вычислительных ресурсов» теперь умещается полностью
- Расширена кнопка switcher'а (margin сторон 8→4 в LeftDrawerMenu), даёт больше места под текст
WorkspaceSwitcher:
- Brand-строка теперь собирается из vars.short_abbr + «vars.name» (получается «ПК «ВОСХОД»»). Раньше показывал только short_abbr — название кооператива было «потеряно»
- Title допускает перенос на 2 строки (line-clamp 2) — длинные названия типа «Стол вычислительных ресурсов» больше не обрезаются ellipsis'ом с одного слова
- Chevron приклеен к верху строки (align-self start) — корректно при двухстрочном title
- Меню workspace: canon padding (8/12) вокруг q-item, отступ между avatar-section и текстом — иконка и название больше не упираются в края
- Затемнение фона при открытом меню (rgba(9,9,11,0.32) overlay через Teleport в body) — меню больше не сливается с контентом за drawer'ом
Bank.vue (карточка платежа):
- Убрали q-expansion-item (раскрытие вниз внутри карточки выглядело перегружено)
- Кнопка-toggle: «Показать реквизиты» рядом с «Скачать QR». Клик переключает зону под сводкой между QR и списком 9 BaseInput'ов с copy-кнопками
- В режиме реквизитов: кнопки меняются на «Скопировать всё» (primary) + «Показать QR» (secondary)
- QR-canvas использует v-show чтобы сохранять состояние при toggle назад; реквизиты — v-if чтобы не держать BaseInput'ы в DOM
ReadStatement: backend-HTML генерирует с inline-стилями и Quasar text-h* классами, которые перебивали :deep правила. Усилили все ключевые селекторы !important + добавили охват .text-h1/h2/h3/h4/h5/h6 и .text-right (под мета-блок «УТВЕРЖДЕНО…»).
Bank.vue (карточка платежа):
- убрали отдельные surface-карточки вокруг сводки и QR (получалось «карточка-в-карточке-в-карточке»)
- сводка → плоский <dl> со строкой-разделителем снизу (border-bottom var(--p-line))
- QR → просто центрированный canvas без обёртки/фона/рамки; белый цвет внутри canvas — функциональное требование контрастного сканирования, не дизайн-выбор
- detail-expansion → без своего фона, только border-top/bottom
WorkspaceSwitcher (новый widget src/widgets/Desktop/WorkspaceSwitcher/):
- Карточка-кнопка в шапке drawer: иконка кооп + caption «ПК «ВОСХОД»» + bold «Стол совета» (текущий) + chevron
- Клик → q-menu со списком всех workspaceMenus (Стол пайщика, Стол совета, Стол председателя и т.д.); выбор → selectWorkspace + goToDefaultPage
- Подключён через #brand slot AppDrawer'а в LeftDrawerMenu, заменяет дефолтный brand-row
ReadStatement (этап 6.1):
- Backend генерирует HTML документа — локально нормализуем через :deep
- h1 уменьшен с монструозного дефолта до canon h3 (20px), центрирован
- h2/h3, p, strong, ul/ol, table, hr — типографика по var(--p-*)
- мета-блок .approved/.meta-right (УТВЕРЖДЕНО…) — ink-2, мельче, плотнее
- Чекбоксы соглашений снизу не трогали (как просил пользователь)
Bank (этап 6.2 — карточка платежа PayWithProvider):
- QR-код вынесен в фокус: сначала сводка (получатель/сумма/назначение) → QR → действия → реквизиты для ручного перевода скрыты под q-expansion-item
- Полные 9 полей (ИНН/БИК/КПП/корр-счёт/...) переведены на BaseInput(readonly, mono) с copy-кнопкой в #append; раскрываются только при необходимости
- Кнопки «Скачать QR» (primary) и «Скопировать реквизиты» (secondary) — BaseButton
- Удалены: q-btn(push), case-mixed «скопировать реквизиты», стиль #qr через global селектор
- .signature-container: 3px primary border + 3-слойный box-shadow glow → 1px dashed по var(--p-line-2), surface-2 фон, без свечения; на hover лишь окрашивается рамка
- .signature-hint: переехала в центр контейнера, canon ink-2 цвет, убран frosted-chip с blur/shadow — теперь спокойная метка «Оставьте собственноручную подпись в рамке», pointer-events: none пропускает клики на canvas
- min-height снижен 300→220, padding на canon-токены
- SignUp.vue: добавлена обёртка .signup-page (padding + center + min-height) — как у SignInPage; раньше страница была прижата к шапке
- AuthCard maxWidth: 1000 → 720 — историческое 1000 для трёх-колоночной UserDataForm; для остальных шагов выглядит абсурдно широко
- q-stepper: убраны собственный фон/тень/padding (не «карточка в карточке»), линии-разделители переведены на var(--p-line)
- EmailInput: max-width поля 360px — email не должен растягиваться во всю ширину карточки
- canon `shared/ui/domain/AuthCard` переписан на pug
- SignUp.vue переключён со старого «глянцевого» AuthCard на canon (title prop вместо CAPS-заголовка)
- старый `shared/ui/AuthCard` удалён (использовался только в SignUp)
- 7 подэкранов SignUp (EmailInput, SetUserData, SelectProgram, GenerateAccount, SelectBranch, ReadStatement, SignStatement): q-btn → BaseButton (ghost для «назад», primary для «Продолжить»)
- EmailInput: q-input → BaseInput, валидация rules переведена на computed :error
- q-input в GenerateAccount оставлен из-за зависимости от ref.select()
- og-image.png — взят brand-постер ~/blago/production/shared/poster-logo-horizontal.png,
отресайзен до 1000px по ширине и центрирован на полотне 1200×630
с белыми краями по вертикали. Шрифт/композиция родные бренда.
- description / og:description / twitter:description: «Цифровой Кооператив —
система управления хозяйством. Регистрация пайщиков, заказы на поставку
и приобретение имущества, собрания совета, общие собрания пайщиков,
взаимные расчёты на блокчейне, автоматический документооборот и бухбаланс
на основе простой электронной подписи.»
- og-image.png перерисован: «Цифровой Кооператив» / «Система управления
хозяйством» / «на платформе БЛАГО — кооперативной экономики для жизни» /
«Регистрация пайщиков · заказы на поставку и приобретение имущества ·
расчёты на блокчейне».
- description / og:description / twitter:description выровнены под этот
же копирайт. Слово «взаимоотношения с пайщиками» убрано как слишком
узкое — управление хозяйством включает в себя ещё заказы, реестры
и документы.
- src/assets/logo.svg, public/favicon.svg, public/logo.svg — единая SVG
логотипа кооператива (path с «ц» внутри). Скачано с
https://цифровой-кооператив.рф из шапки сайта.
- src/assets/logo.svg: fill -> currentColor чтобы лого внутри
canon .rail__brand наследовал --p-primary (для тёмной темы тоже).
- LeftDrawerMenu подключает лого через slot #brand-icon AppDrawer
(импорт ?raw + v-html). Внутри зелёного квадрата шапки рейла теперь
наш системный знак, а не дефолтный q-icon "dashboard".
- index.html: favicon → /favicon.svg, apple-touch-icon тоже на SVG.
Удалены неработающие ссылки на /favicon.ico и /apple-touch-icon-*.png.
- index.html: добавлены description + расширенные og:* и twitter:*
с конкретным описанием и og-image (1200×630), чтобы ссылки в чатах
выглядели прилично.
- public/og-image.png — превью 1200×630, ImageMagick-сгенерированный
композит из логотипа + текста.
- Удалены неиспользуемые ассеты:
- public/icons/, public/pwa/ — старые favicon/PWA-PNG'и всех размеров;
- src/assets/* — 40 легаси SVG/PNG (anime, blockchain, dacom*, flow*,
header-logo, quasar-logo-vertical, system*, club*, welcome.jpeg и т.д.).
Оставлены только src/assets/pin.svg (используется в Map.vue) и
src/assets/logo.svg (новый).
Pug-template передаёт выражения в Vue compiler как строки; конструкции
вида (entry as RailItem).route не превращаются в JS и падают
SyntaxError в браузере: «Unexpected identifier 'as'».
Касты унесены в <script>: добавлены computed flatItems и
normalizedGroups, шаблон работает с уже типизированными массивами без
inline-TS.
- LeftDrawerMenu: импорт useCmdkStore → useCmdkMenuStore (правильное имя).
Без этого ломался ESM build всего drawer'а: «no exported member
useCmdkStore» — RailUserCard не монтировался, поэтому и chevron свёртки
«не работал», и Найти/Пополнить не реагировали.
- AppDrawer.vue, AppHeader.vue, RailUserCard.vue переведены на pug
(lang="pug"). В проекте везде pug — никакого исторического HTML в
canon-компонентах быть не должно.
- q-drawer width 240 → 248px (совпадает с canon --p-rail-w, кошелёк больше
не уплывает справа из-за обрезки на 8px)
- LeftDrawerMenu: убран весь :deep override на .rail__usercard/.rail__signout,
возвращаем canon margin/padding как есть
- LeftDrawerMenu: добавлен v-model:collapsed на RailUserCard с сохранением
в localStorage — chevron «свернуть/развернуть» снова работает
- LeftDrawerMenu: кнопка Найти теперь дёргает cmdkStore.openDialog() напрямую
(фейковый KeyboardEvent не доходил до глобального обработчика)
- LeftDrawerMenu: «Пополнить» → useDepositDialog().open(); DepositButton +
WithdrawButton рендерятся скрыто как держатели q-dialog (q-portal в body)
- Header.vue и LeftDrawerMenu.vue переписаны на pug (как остальной проект)
- AppDrawer: плоский items оборачивается в .rail__nav, появляется gap 4-8px до cmdk
- cmdk margin 16→8px, чтоб выровнять с rail__nav (8px от стенки)
- LeftDrawerMenu footer: WalletCardMini+LogoutButton → один canon RailUserCard
(avatar+balance в primary-soft + Пополнить + встроенный signout)
- :deep override на .rail__usercard margin 16→12/8 и .rail__signout padding 12/20→12/12
- quasar-canon: body--light/dark + q-layout/q-page-container/q-page красятся
через --p-canvas, чтобы фон страницы соответствовал шапке и рейлу
LeftDrawerMenu:
- legacy `MicroWallet` (ColorCard teal с большой карточкой пайщика + ИП-badge
+ Deposit/Withdraw micro-кнопки) → canon `WalletCardMini` из
widgets/wallet-card-mini (тонкая карточка из shared/ui/domain/WalletCard
compact-variant, читает walletStore сама).
- Убран toggle «свернуть/развернуть нижнюю секцию» — нижняя секция теперь
всегда видна (короткий компактный блок: WalletCardMini + Выйти).
- Удалены неиспользуемые `onMounted`/`ref` импорты, slide-анимация и CSS
toggle-кнопки.
LogoutButton:
- pug + q-item с красной полупрозрачной плашкой → плоский ghost-button в
стиле rail-пункта AppDrawer: прозрачный фон в покое, на hover —
`--p-neg-soft` фон + `--p-neg` текст/иконка.
- Из uppercase «ВЫЙТИ» в title-case «Выйти» (canon — никаких uppercase).
Визуально левый drawer теперь полностью в canon-семье: единый rail с
пунктами + поиском + компактным wallet-балансом + ghost-кнопкой выхода.
Закрывает этап 3 из 5 в волне «приземление DS на реальный layout».
Дальше — SignUp (этап 4) и Invite (этап 5).
widgets/Header/CommonHeader/Header.vue теперь использует canon AppHeader
из shared/ui/layout. Высота шапки 56px (--p-topbar-h), border-bottom
hairline, без shadow и фоновых градиентов.
Слоты:
- #crumb: BackButton (для авторизованных), либо title с названием
кооператива из system.info.vars для гостей/install.
- #actions: headerActions injection через useHeaderActionsReader (всё что
страницы инжектят — Deposit/Withdraw, SettingsDropdown и пр. — рендерим
как было).
- #notifications: NotificationCenter (только loggedIn + isClient).
- #theme: ToogleDarkLight — пока legacy, сохраняет storage + PWA-цвет;
наш canon ThemeToggle их не делает (привяжу позже отдельным эпиком).
- #profile: BaseButton primary для гостей — Регистрация/Вход (в зависимости
от текущего route).
Удалены:
- MainHeader.vue (вся логика scroll arrows + carousel actions group ушла —
canon `.topbar__actions { gap: 8px }` достаточно; overflow-scroll
будет вернут отдельно если потребуется на узких экранах).
- HeaderStyles.scss (стили scroll-arrow и q-toolbar overrides больше
не нужны — canon-разметка через `<header class="topbar">`).
q-header Quasar остаётся wrapper'ом (sticky-poзиционирование в q-layout),
внутри — AppHeader даёт canon-разметку.
Переписан widgets/Desktop/LeftDrawerMenu/LeftDrawerMenu.vue: внутри теперь
canon `AppDrawer` из shared/ui/layout с rail-видом MONO v2 (логотип ПК +
короткое имя кооператива в шапке рейла, ⌘K-поиск, пункты-router-link'и
с иконкой/бэйджем, sticky-footer).
Адаптер store→canon:
- items: desktop.activeSecondLevelRoutes → RailItem[], с прежней
фильтрацией по roles + meta.conditions + meta.hidden (один-к-одному
логика из SecondLevelMenuList).
- activeKey: вычисляется из router.currentRoute с поддержкой группового
паттерна project-* → projects-list.
- @select: router.push({name, params:{coopname}}) или
actionsStore.executeAction(meta.action), закрытие drawer на mobile.
- @cmdk: эмиттим keyboard event ⌘K → CmdkMenu (он смонтирован глобально
в default.vue) подхватывает.
Footer-слот: пока сохранён legacy MicroWallet + LogoutButton + toggle
свернуть/развернуть (заменим на WalletCardMini в этапе 3).
default.vue: q-drawer width 200 → 240 (canon-ширина rail'а).
Что НЕ затронуто этим коммитом: верхняя шапка (legacy MainHeader),
правый drawer для actions, q-footer для гостей. Их меняем отдельными
этапами 2–3.
Корневая причина «оторванной» галочки в ResetKeyForm: я ранее задал в
mono-platform/components.css глобальное `.row { display: flex;
align-items: center; gap: 12px; }`. Quasar внутри `q-checkbox` (а также
q-radio, q-toggle, q-btn-toggle и др.) сам добавляет class="row" на
корень компонента и использует его как inline-flex — мой `gap: 12px`
раздвигал `.q-checkbox__inner` и `.q-checkbox__label` на 12px по всему
приложению.
Решение:
- Переименовать утилиту `.row` → `.u-row` (та же семантика, тот же набор
модификаторов --wrap, --gap-2/4/6). У Quasar теперь свободный .row.
- Обновить все 6 использований в /_dev/ui.
- Откатить предыдущий :deep(.q-checkbox__label) padding-left:4px workaround
в ResetKeyForm — лечил симптом, причина устранена.
Затрагивает только нашу dev-витрину и canon-utility; legacy pug-виджеты
с `.row.justify-center` продолжают работать через стандартный Quasar
flex-grid класс.
Quasar по умолчанию даёт ~12–16px padding на .q-checkbox__label, в
compact-форме сохранения ключа это смотрелось «оторвано». Уменьшаем до 4px
через :deep override на классе .rk-form__confirm.
Три кадра в одной секции: LostKey (ввод email), ResetKeyForm check-mail
(уведомление «письмо отправлено»), ResetKeyForm save-key (демонстрация
сохранения ключа с моковым сгенерированным аккаунтом). Submit на третьем
кадре регенерирует мок-ключ для повторной проверки.
Заменяет ошибочную секцию ChangeKey из предыдущей итерации.
ChangeKey-фича (формы current/new/confirm WIF + успешный диалог) была
ошибочной интерпретацией E7. Реальный flow «сменить ключ» в продукте —
это **двухэтапный ResetKey**:
1. LostKey: пайщик вводит email (он потерял ключ — текущего WIF нет),
бэк присылает письмо со ссылкой `?token=...`.
2. ResetKey: при заходе по ссылке клиент сам генерирует новый ключ
прямо в браузере (через `useCreateUser().generateAccount()`),
показывает приватный ключ readonly с «Скопировать», требует
подтверждения «Я сохранил ключ» и вызывает on-chain
`resetKey({token, public_key})`.
Текущий ключ ввести нельзя (его нет). Новый ключ ввести нельзя (только
генерируется). Никакого подтверждения нового ключа — он один и тот же.
Что сделано:
- Удалена ошибочная `features/User/ChangeKey/`.
- Старый `widgets/Registrator/ResetKey/ui/ResetKey.vue` (pug + сырые
q-input/q-checkbox + кастомные градиентные стили) разбит на:
* `ResetKeyForm.vue` — presentation (props-driven, режимы
`check-mail` / `save-key`), без useRouter/useApi — пригоден для
/_dev/ui витрины.
* `ResetKey.vue` — connected обёртка, читает `route.query.token`,
генерит account через `useCreateUser`, диспатчит `resetKey` через
`useResetKey`, на успех → router.push на signin.
- Новая UI собрана на BaseForm + BaseInput readonly mono + BaseButton +
BaseBanner + AuthCard.
/_dev/ui секция 20 теперь показывает три кадра подряд: LostKey (email
шаг), ResetKey check-mail, ResetKey save-key с моковым сгенерированным
ключом.
FR11, UX-DR9, UX-DR15.
Generic-проброс `<template v-for="(_, slotName) in $slots" #[slotName]>`
рушится в Quasar 2.19 рендере: при наличии scoped-слота #append вылетает
`Cannot read properties of null (reading 'key')` в renderSlot + Vue warns
по `_isVue`/`constructor` свойствам. Заменяем на явный список слотов
(prepend / append / before / after / hint у q-input; +option, selected-item
у q-select) — синтаксис, который Quasar держит корректно.
Кейс всплыл на ChangeKeyForm: #append с q-icon (toggle visibility) ронял
страницу /_dev/ui целиком.
Подключение ChangeKeyForm + ChangeKeySuccessDialog к dev-витрине.
Submit запускает заглушку useChangeKey().changeKey() (≈600 мс), по успеху
открывает диалог с новым WIF. Generate генерирует валидный K1 ключ через
SDK и заполняет оба новых поля. Confirmed сбрасывает форму.
Диалог успеха смены ключа: новый WIF в моноширинном блоке с copy-кнопкой,
BaseDialog с closeOnBackdrop=false, closeOnEscape=false, hideCloseButton=true.
Закрытие только через primary-кнопку «Я сохранил ключ»; до копирования
кнопка показывает guard-баннер вместо немедленного закрытия. По confirmed
эмитим событие — реальный flow подключит router.push на дашборд.
FR11, UX-DR11, UX-DR15. Story 7.2 из epics.md.
Форма смены ключа пайщика на новых компонентах дизайн-системы.
BaseBanner severity=warn с предупреждением о потере, checklist последствий,
поля current/new/confirm WIF моноширинные с toggle visibility, ghost-кнопка
«Сгенерировать» через PrivateKey.generate из @wharfkit/session.
Бизнес-логика — заглушка с TODO на привязку on-chain change-key action
(pattern см. features/User/ResetKey/api) и TODO на OtpInput шаг подтверждения
(приходит в Эпике 10).
FR11, UX-DR9, UX-DR15. Story 7.1 из epics.md.
Bug после rename --p-accent → --p-primary: класс .avatar--accent смотрел
на --p-primary-soft (canon teal), а должен был — на --p-accent-soft
(warm orange по новой семантике). И вариант ink (белый bg + dark text)
визуально выпадал из палитры — крайний xl-аватар на демо-витрине казался
«не пришей к звезде рукав».
Новая семантика AvatarTone:
- neutral (default) — серый surface-3 + ink-1 (нейтральный)
- primary — canon-teal soft + canon-teal text (основной выделение)
- accent — warm-orange soft + warm-orange text (редкое выделение,
например подсветка пайщика по batch_id)
Удалён вариант ink — не использовался в реальном коде.
В dev-витрине xl-аватар: tone="ink" → tone="primary".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Override (padding-top:0 + align-self:center на dense) не отрабатывал —
Quasar в dense держит prefix/suffix по baseline вводимого значения,
и пользователю это устраивает. Убираем неработающий override, чтобы
не мешался в дальнейших правках.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Inline `style="margin:0"` на <p>-описании перебивал мой margin-bottom
через CSS (специфичность inline > selector). Переключаюсь на
`> :first-child + *` margin-top — отступ крепится ко второму ребёнку,
не зависит от inline-стилей первого.
Работает универсально: <p> + <BaseInput>, <p> + <BaseForm>, любая
другая пара «описание + контент».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. --p-accent (canon token): #D84315 (light) / #FF7043 (dark).
Совпадает с legacy Quasar $accent (warm orange, Material Deep Orange).
Проброшен в --q-accent через CSS var — legacy color="accent",
var(--q-accent), text-accent продолжают работать без изменений,
но теперь следуют за canon-темой (light/dark). Резерв под редкое
выделение: ссылки batch_id и т.п.
2. quasar-canon.css: .q-field--dense .q-field__suffix/prefix —
padding-top:0 + align-self:center. Quasar по умолчанию ставит
padding-top: 24px под floating-label, отчего «RUB» визуально
опускался к низу control'а. Теперь в одну линию со значением.
3. BaseForm: убран gap 12px из .base-form__body — reserve-hint-space
у q-input даёт ~24px снизу под error/hint, дополнительный gap
делал расстояние между инпутами неприятно большим.
4. BaseDialog: убран универсальный gap из body, оставлен margin-bottom
только на первом <p>/.intro — между описанием и первым инпутом
воздух нужен, между остальными детьми reserve-hint-space хватает.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Промахнулся `git add -A` — захватил worktree-symlink node_modules
(он pnpm-shared с основным репо). .gitignore покрывает node_modules/
но симлинк-файл — отдельный путь.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Контекст: после проброса --q-primary = var(--p-primary) (4fddfe2343)
наш токен --p-accent и Quasar primary стали обозначать ОДИН и тот же
canon-teal — два имени для одного цвета это когнитивный шум, и при
росте платформы будет путать. Имя `accent` оставляем свободным под
будущий ВТОРОЙ цвет (warm-orange, для редкого выделения вроде ссылок
по batch_id) — это отдельная задача когда понадобится.
Переименовано везде (109 ссылок в 6 файлах):
- --p-accent → --p-primary
- --p-accent-hover → --p-primary-hover
- --p-accent-press → --p-primary-press
- --p-accent-soft → --p-primary-soft
- --p-accent-line → --p-primary-line
- --p-accent-strong → --p-primary-strong
- --p-ink-on-accent → --p-ink-on-primary
Файлы: tokens.css, quasar-canon.css, components.css, RailUserCard,
AuthCard, _dev/ui (включая user-facing labels палитры «Accent → Primary»
и заголовок секции «Акцент → Основной цвет»).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- tokens.css: проброс canon-палитры в Quasar brand-переменные
(--q-primary/negative/positive/warning/info = соответствующие
--p-* токены). Резолвится lazy → автоматически следует за темой.
Теперь Quasar САМ красит focus q-field, q-btn color="primary",
bg-primary utility-классы в canon-цвета без CSS-overrides;
- quasar-canon.css: убраны border-color overrides на ::before
(default/focus/error) — Quasar управляет цветом через --q-primary
и собственный механизм --focused/--highlighted. Оставлены только
background, text-color и rounded углы (border-radius var(--p-r-sm));
- BaseInput/Select: вернули `dense` (компактный 40px) и явный
`color="primary"` (canon teal через --q-primary).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Между нижним инпутом и footer-кнопками был ~32px (q-card-section
padding-bottom 20 + q-card-actions padding-top 12) — выглядело
растянуто. Сводим к 16px (8+8) — кнопки сидят ближе к контенту.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Регрессия мерцания границы и приплюснутости — следствие борьбы наших
overrides с встроенной Quasar анимацией. Возвращаем дефолт: Quasar
сам решает геометрию (height/padding/border-radius/font), анимации,
opacity ::before и focus-индикацию. Наша задача — только покрасить
в canon-цвета.
Удалено из .q-field--outlined:
- height/padding/border-radius/font-size/font-family overrides;
- transition border-color (Quasar сам делает свой transition);
- opacity:1 !important на ::before (был костыль от мерцания);
- box-shadow:none на --focused/--highlighted;
- display:none на ::after underline;
- override .q-field--dense;
- override .q-field--float .q-field__label (Quasar анимирует label сам);
- focus-shadow / outline reset на native input.
Оставлено (только цвет):
- background: var(--p-surface);
- border-color: line-1 / accent (focus) / neg (error);
- color текста, placeholder, label, prefix/suffix, hint/error.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Quasar при focus анимирует opacity ::before (прячет старую границу
перед показом ::after underline). Так как ::after мы спрятали,
получался видимый gap ~200-400 ms «граница исчезла → появилась
с подсветкой». Фикс: opacity:1 !important на ::before,
transition только по border-color.
- BaseTable.vue: убран неиспользуемый импорт BaseTableColumn.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- BaseInput/BaseSelect: убран `dense` — возвращаемся к дефолтному
outlined Quasar (control ~56px). Floating-label получает достаточно
воздуха, текст значения визуально центрируется без сжатия;
- BaseDialog: `.base-dialog__body` теперь flex-column с gap 16px —
описание/инпуты/таблицы внутри диалога получают единый воздух
без правки потребителя (пример: «Паевой взнос: укажите сумму…» →
отступ до инпута появляется автоматически);
- BaseForm: `.base-form__body` gap 4px → 12px — те же требования
внутри формы;
- WalletCard: `.wallet__metric-val` 19→17px — итоговое значение
для аккуратного баланс-блока FULL.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. Stack-label: BaseInput/BaseSelect → только `dense`. Quasar
стандартно сам корректно держит floating-label в dense+outlined,
без CSS-override метка работает по документации Quasar.
2. Двойная focus-подсветка: убран наш box-shadow var(--p-focus-ring)
поверх Quasar внутренней focus-индикации (виден был как «белый
ринг» на тёмной + наш teal). Теперь только border-color меняется
через ::before (canon-минимализм). Дополнительно `box-shadow: none
!important` на --focused/--highlighted control и `display:none` на
::after underline (на случай dense Quasar variants).
3. Mini-wallet в правом верхнем углу AppHeader: слот #wallet удалён
из API (баланс уже отображается в RailUserCard в drawer'е,
дубль в шапке избыточен). Убран из dev-витрины каркаса.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Регрессия после Quasar wrapper-pivot: метрики кошельков выглядели
крупно по сравнению с заголовком/sub-label, а padding'и были
избыточны для compact-варианта.
- .wallet (FULL): padding 18→14px по верт, иконка 44→40px,
metric-val 22→19px, ccy 13→12px;
- .wallet--row (compact): padding 14→10px, иконка 36→32px,
metric-val 18→16px — выглядит как row в списке, а не как карточка;
- .rail__balance-val (mini-wallet в drawer): 22→18px — теперь не
доминирует над identity-блоком пайщика.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень регрессии: жёсткий height: 40px в .q-field--outlined раздавил
вертикальную разметку q-input (Quasar держит padding-top:24px под
floating-label слот; при сжатии текст значения сдвигается). Те же
эффекты для select.
- BaseInput/BaseSelect → dense + stack-label по умолчанию;
Quasar сам корректно держит 40px в dense, метка статически стоит
над инпутом без floating-механики;
- .q-field--outlined: убран принудительный height; .q-field--dense
получает только padding-x, высоту контролирует Quasar;
- .q-btn: font-weight 500→450, min-height 40→36px, padding 16→14px —
кнопки больше не выглядят «жирно/тяжело»;
- .q-btn--outline: hairline-граница через ::before (Quasar pattern),
без двойной рамки;
- размеры sm/lg/dense пропорционально уменьшены;
- .q-chip: высота 24→26px, padding 10→12px, font-weight 500→450;
+ .q-chip__icon размер 14px (для точечных чипов).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseSelect → q-select (outlined, reserve-hint-space, map-options/emit-value
для совместимости с {value,label} options API).
BaseCard → q-card flat с q-card-section head/body. Variants flat/inset/quiet
override-ят через scoped style.
BaseChip → q-chip с color mapping (variant → primary/positive/negative/
warning/info). Square для canon-look.
BaseBadge → q-badge с тем же color mapping. Dot-вариант через scoped style
(8×8 круг).
BaseBanner → q-banner rounded dense с border-left по variant.
BaseDialog → q-dialog + q-card с size→maxWidth mapping. closeOnBackdrop/
closeOnEscape пробрасываются через :persistent / :no-*-dismiss.
BaseTable → q-table с трансформацией columns в QTableProps['columns'].
Hide-pagination, binary-state-sort. Cell-slots работают через body-cell-{key}.
AuthCard (domain) — был на native canon `.card` div. Переведён на q-card flat
для консистентности. Усиленный shadow + accent-stripe сверху сохранены.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseInput теперь рендерит <q-input outlined reserve-hint-space no-error-icon>
с проброшенными slots. reserve-hint-space от Quasar резервирует место
под error/hint встроенно — раньше делали вручную через NBSP. Все Quasar
features (rules через v-bind \$attrs, mask, ref API, native slots) работают
прозрачно. Props API совместим (modelValue, label, hint, error, prefix,
suffix, mono, readonly, disabled, autocomplete, name, id, type).
BaseButton — q-btn с variant→Quasar mapping:
primary → :unelevated color="primary" (заливка accent)
secondary → :outline (border + surface)
ghost → :flat (transparent)
danger → :flat color="negative" (transparent + neg-cвет)
:no-caps :ripple=false для canon-look. Props API совместим.
BaseForm — q-form-обёртка с сводным error-баннером (q-banner) и slot footer.
banner—neg styling прилетает из quasar-canon.css через .bg-negative-soft.
Глобальная стилизация Quasar в quasar-canon.css даёт canon-look всем
этим компонентам и существующим q-input/q-btn/q-card по всему проекту.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Архитектурный pivot: вместо native HTML5-компонентов под canon-классами
стилизуем Quasar поверх. Причины:
- Сохраняется весь Quasar API из коробки (rules/mask/dense/slots/refs)
- Существующий код (сотни q-input, q-btn, q-card, q-select) автоматом
получает canon-look без правок
- Меньше регрессий — Quasar отвечает за accessibility/keyboard/focus
Файл src/css/mono-platform/quasar-canon.css содержит overrides под canon
для: q-field--outlined (q-input/q-select), q-btn (unelevated/outline/flat),
q-card, q-chip, q-badge, q-banner, q-dialog/q-menu, q-item, q-table,
q-checkbox/q-toggle.
Подключен в quasar.config.cjs css[] после components.css. tokens.css
без изменений — все цвета/радиусы/тени тянутся как переменные.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Истинный источник «прыгалки» AuthCard на hover: глобальное правило
`.card:hover { transform: scale(1.03) }` в src/app/styles/style.css
(и его дубль в pages/Marketplace/MainPage/ui/MainPage.vue, тоже не scoped)
бьёт по любому элементу с классом card. AuthCard имеет корневой
<div class="card auth-card"> — каждый hover дёргает её на 3%.
Поиск показал: класс `card` без модификаторов в templates никто кроме
новых canon-компонентов (BaseCard, AuthCard) не использует. Этот глобал —
мёртвый legacy, удаляем безопасно.
NBSP-фикс layout-shift из предыдущего коммита (2a2d9a786a) остаётся
актуальным — он лечил другую (на уровне поля) проблему.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseInput: контейнер field__message теперь рендерится всегда с min-height
calc(--p-fs-meta * 1.4) — раньше div появлялся/исчезал на каждом keystroke
при валидации (см. LostKey emailError), форма прыгала вверх-вниз.
Когда нет ни ошибки, ни хинта — рендерится NBSP, высота сохраняется.
AuthCard: добавлен box-shadow поверх canon-минималистского .card —
canvas #fafafa и surface #ffffff на светлой почти неразличимы, без тени
карточка выглядит плоской. На тёмной отдельный shadow усилен (поверх
canon `--p-shadow-card`, который оптимизирован под data-cards, не hero).
border-color подкручен с --p-line на --p-line-1 для большей чёткости.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Scope-adjustment: plan'овая Story 6.3 предполагала миграцию SignUp как
второго auth-flow, но Register — multi-step wizard (10+ файлов в
pages/Registrator/SignUp/), который требует VerticalStepper из Эпика 10
и не помещается в Волну 1. Вместо этого мигрируем LostKey — простую
одно-полевую auth-страницу, того же auth-домена.
LostKey (widget): pug → html, старый AuthCard (q-card с shimmer) →
canon AuthCard. q-input → BaseInput с inline email-валидацией;
q-btn → BaseButton; всё обёрнуто в BaseForm с loading/error.
Бизнес-логика (useLostKey.startResetKey + router redirect на resetkey)
сохранена 1:1.
LostKeyPage: footer-кнопка «Назад» через ghost BaseButton в footer-slot
AuthCard; центровка через flexbox.
Не мигрировано в Волне 1 (известный gap):
- SignUp wizard (требует VerticalStepper из E10)
- ResetKey / Invite (на старом AuthCard, мигрируются в Волне 2)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
LoginForm (feature): q-input → BaseInput, q-btn → BaseButton, обёрнут в
BaseForm с управлением loading/error. Бизнес-логика (useLoginUser, redirect,
OpenReplay tracking, NotificationPermissionDialog) сохранена 1:1.
Ошибки входа теперь показываются через сводный error BaseForm
(плюс legacy FailAlert остаётся как fallback).
SignIn (widget): импорт AuthCard переключён на новый canon-композит
(src/shared/ui/domain/AuthCard), убран pug-template и градиентные стили.
Добавлен footer-slot — пробрасывается в AuthCard.
SignInPage: переписан с pug → html, footer-кнопки «Потеряли ключ?» /
«Нет аккаунта?» вынесены в footer-slot AuthCard через BaseButton ghost-sm.
Центровка через flexbox вместо QRow grid.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AuthCard (src/shared/ui/domain/AuthCard) — canon .card + center-aligned
head (title/subtitle/slot head) + body-slot + опциональный footer-slot.
Старый src/shared/ui/AuthCard (q-card с shimmer-градиентом) остаётся
для не-мигрированных страниц (SignUp wizard, ResetKey, Invite).
AuthLayout (src/app/layouts/AuthLayout.vue) — wrapper-обёртка для full-page
auth-страниц: фон var(--p-canvas), центрирование, тогл темы в правом верхнем.
Витрина: dev-секция 19 с примером login-формы (BaseForm + BaseInput ×2 +
BaseButton + footer-ссылки).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WalletCardMini подключает WalletCard к Pinia (useWalletStore.program_wallets) +
System store (info.symbols.root_govern_symbol) + useRouter (клик →
name: 'wallet'). Программный mapping: wallet→MAIN, blagorost→BLAGOROST,
generator→GENERATOR (Zeus.ProgramType). Loading определяется автоматически
по наличию данных в store (или пробрасывается через prop).
Подключено в слот wallet AppHeader на dev-витрине (секция 16 — каркас) +
выделенная секция 18 с тремя программами. На dev-странице покажет
skeleton-loading т.к. loadUserWallet не вызывался; на реальных страницах
после init процесса — настоящий баланс.
Со-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Благорост — капитализация РИД (не «накопления на жильё»), Генератор —
генерация РИД (не «доходы от программы»). Кошелёк остаётся «свободный
остаток». Черновые ярлыки риском попадают в production через копипасту.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Канонический .wallet с компактным (--row) и full-вариантами, программные
акценты через --prog-blagorost / --prog-wallet / --prog-generator (UX-DR20).
Витрина на /_dev/ui секция 17: три программы × оба варианта + loading +
empty + locked-line.
Compact (.wallet--row): 36px icon, 18px sum, 14px padding — для слота шапки.
Full (.wallet): 44px icon, 22px sum, 18px padding — для дашборда.
Connected-обёртка widgets/wallet-card-mini (Story 5.2) делается отдельным
коммитом, требует интеграции с реальным wallet-store и MARKETPLACE_ASSET_CONFIG.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Анимация max-height + opacity при свёртке давала видимый прыжок крупной
суммы баланса (`font-size: 22px` + `display: flex; align-items: baseline`):
во время промежуточных кадров baseline переcчитывался относительно
схлопывающегося контейнера → текст «прыгал в середину» и обратно.
Возвращаемся к canon `display: none`. Resize моментальный, без skipping.
Поведение rail (top опускается, bottom-chevron остаётся через flex-spacer)
работает и без анимации — пользователь это и подтвердил в фидбеке.
Chevron rotation через :deep(.q-icon) и balance-route не трогаем —
они независимы.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. **Палитра системы.** Новая секция «00» в шапке dev-страницы со всеми
основными --p-* токенами:
- Акцент (accent / hover / press)
- Поверхности (canvas / canvas-2 / surface / 2 / 3)
- Текст (ink / ink-on-accent)
- Линии (line / 1 / 2) — текстовый input для rgba
- Статусы (pos / neg / warn / info)
- Программные тинты (blagorost / wallet / generator)
Color-picker для hex-токенов, текстовый ввод для rgba. Live-override
через `document.documentElement.style.setProperty()` — inline-style на
:root перебивает CSS-правила, перекрашивание мгновенное. Подсветка
переопределённых токенов + счётчик в шапке + кнопка «Сбросить» (через
removeProperty). Watch на Quasar Dark — при смене темы пикеры
обновляются на новые resolved-значения.
2. **Chevron в свёрнутом виде — full-width.** Из-за того что для плавной
анимации primary остаётся в DOM (max-height:0 вместо display:none),
у него сохранялся `flex: 1` и он съедал ширину карточки → chevron не
мог растянуться через canon-правило `width: 100%`. В `.is-collapsed`
добавили `flex: 0 0 0` + `width: 0` для primary — chevron теперь
корректно занимает всю ширину.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
По фидбеку:
1. **Chevron не переворачивается.** Canon-правило
`.rail__usercard__collapse svg { transform: rotate(180deg) }` рассчитано
на `<svg>`, а Quasar `<q-icon>` рендерит `<i class="q-icon">`. Прокидываем
transition + rotate через scoped `:deep(.q-icon)` — chevron теперь крутится
при свёртке/развёртке.
2. **«Карточка улетает»** — резкий jump при `display:none` создаёт визуальный
прыжок. Подменяем display:none на `max-height: 0` + `opacity: 0` с
transition'ом. Логика canon-селектора `.is-collapsed` не нарушается,
просто добавлена плавность. Если структурное поведение rail (top
опускается, bottom-chevron остаётся на месте) всё равно неудобно —
перевёрстаем во вторую итерацию.
3. **Клик по балансу → маршрут кошелька.** Новый prop `balanceRoute` —
если задан, блок `.rail__balance` оборачивается в `<router-link :to>`.
Параллельно эмитим `balance-click` — для аналитики или функционального
handler'а. На витрине привязал к `/_dev/ui#wallet` чтобы проверить.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. `RouteMeta.icon` объявлен обязательным в `src/env.d.ts` — добавили
`icon: 'fa-solid fa-flask'` для dev-роута, иначе vue-tsc валится.
2. `RailUserCard` (`shared/ui/domain/RailUserCard`) — доменный
общеплатформенный компонент мини-кошелька в нижней части drawer'а.
Это будущий E11 (Волна 2), но логично вытащить раньше, чтобы основной
layout можно было заменить целиком уже сейчас.
Реализован по canon строго: rail__usercard с usertop+balance+actions,
collapse-chevron справа с поворотом arrow при свёртке (через
`.is-collapsed` модификатор + canon CSS), отдельный `rail__signout`
блок (выходит вне `.rail__usercard` — рендерится сразу после, в том
же `aside.rail` через footer-slot).
Контракт строго dumb по архитектуре: только props/emits/slots.
- props: name, role, avatarSrc, balance, symbol, balanceLabel,
lockedBalance, lockedLabel, primaryActionLabel, collapsed,
showSignout, signoutLabel
- emits: primary-action, update:collapsed (v-model), signout
- slots: usertop-extra, actions
Coнnected-обёртки (с инжектом store/auth) — на уровне страницы,
не здесь.
3. Витрина `/_dev/ui`:
- Секция 12 (AppDrawer): заменили временный огрызок rail-demo__user
на полноценный `<RailUserCard show-signout>` через footer-slot.
- Секция 16 (Каркас приложения): аналогично — теперь у rail полноценный
footer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
E4 «Layout-каркас» Волны 1: 4 структурные обёртки в `src/shared/ui/layout/`.
По архитектуре — только props/emits/slots, без store/router/api, без знания
о данных. Cтраницы передают menu-items, breadcrumbs, табы и активные ключи
сверху.
Состав:
- `AppDrawer` — canon `.rail`: solid surface, активный пункт soft-accent
пилюлей + 2-px рейл слева. Поддерживает плоский список + секции
(RailSection с eyebrow-меткой), opt-in ⌘K-кнопку, slot `footer` для
user-card / logout.
- `AppHeader` — canon `.topbar`: бургер · crumb · `topbar__actions` ·
`topbar__right`. Поддерживает простой title и multi-level breadcrumb.
Глобальные действия через слоты `notifications`/`theme`/`wallet`/`profile`
или общий `right`.
- `PageHead` — canon `.page-head`: eyebrow + title + subtitle + slot
`actions`. Используется когда страница БЕЗ подстраниц.
- `PageTabs` — canon `.tabbar` + `.tab`: вкладки подстраниц с tab__count,
опциональный hairline-разделитель и `tabbar__actions` справа.
Также в составе коммита:
- Убрали AuthCard из base — он композит (cap + body + head + footer),
переедет в E6 Auth flow рядом с SignUp/ResetKey/Invite.
- BaseForm: gap полей 16→10px (тесные формы как в каноне).
- Витрина `/_dev/ui`: 4 новые секции (12-15) на каждый layout-компонент +
секция 16 «Каркас приложения» — AppDrawer + AppHeader + PageTabs в живой
связке без legacy QLayout/QDrawer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Скрипт извлечения из bundled HTML оставил ` <style>...` в начале и
`</style>` в конце обоих файлов. CSS-парсер ловил `<` как ошибочный
селектор и пропускал блок до ближайшей `}` — съедая весь первый блок
правил.
В tokens.css первым блоком был `:root, [data-theme="light"]` со светлой
палитрой — она целиком игнорировалась. `[data-theme="dark"]` ниже
парсился нормально, поэтому тёмная тема работала, а светлая — нет:
кнопки primary/ghost/danger оставались с transparent background и
сливались с canvas.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заносим эталонную дизайн-систему в проект — только базовые элементы для
дальнейшей компоновки. Source — MONO Design System.html.
Архитектура (по `_bmad-output/planning-artifacts/architecture.md`):
- `src/css/mono-platform/{tokens.css,components.css}` — canon токены и стили
компонентов 1:1 из эталона; импортируются первыми в quasar.config css[].
- `src/shared/ui/base/<Имя>/{Vue,types,index}` — 14 базовых обёрток (Vue 3 +
TS, native HTML над canon BEM, no Quasar-deps кроме Dark API в ThemeToggle).
FSD-граница: только props/emits/slots, никаких store/router/api.
- `src/boot/ui.ts` — глобальная регистрация 14 базовых компонентов.
- `src/boot/theme.ts` — sync `html[data-theme]` ↔ Quasar Dark.
- Inter + JetBrains Mono в index.html.
14 базовых:
BaseButton, BaseInput, BaseSelect, BaseCard, BaseTable, BaseChip, BaseBadge,
BaseDialog, BaseBanner, BaseForm, EmptyState, Avatar, ThemeToggle, AuthCard.
Витрина `/_dev/ui` (dev-only) — каждая обёртка отдельной секцией над чистым
canon-канвасом, без layout-обёрток. Маршрут смонтирован на корневом уровне,
ВНЕ DynamicLayoutWrapper, чтобы legacy default.vue не перебивал стили.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Boot-файл haptics: при reload страницы на устройстве с Vibration API (мобильный
браузер / PWA на Android) коротко вибрирует (15 мс). Reload определяется через
Navigation Timing (с фолбэком на legacy API), чтобы не срабатывать на первой
загрузке/навигации. Полностью безопасно для десктопа и обычных сайтов: где
Vibration API нет — вызов не делается, любая ошибка проглатывается, на загрузку
приложения не влияет. Только клиент (гард для SSR).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
В левом дровере был маленький горизонтальный скроллбар, и трекпадом его можно
было «оттянуть», обнажая правую границу — дровер выглядел скроллируемым, хотя не
должен. Причина: у .rail собственный border-right (1px), который при content-box
даёт ~1px overflow внутри контента дровера. Фикс в .app-left-drawer: overflow-x
hidden + overscroll-behavior-x contain на .q-drawer__content, .rail приведён к
box-sizing border-box и width 100%.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Если проголосовать и сразу нажать «Утвердить», утверждение падает с ошибкой —
бэкенд ещё не учёл голос из блокчейна. После успешного голоса держим состояние
загрузки пункта (а значит и кнопку «Утвердить» в :loading) ещё VOTE_SETTLE_MS
(3с), за это время голос успевает обработаться. На ошибке загрузка снимается
сразу. Заодно ручное мутирование processingDecisions + setTimeout-хак заменены
аккуратным реактивным хелпером setProcessing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Повестка совета показывает все НЕутверждённые вопросы. Голос «за»/«против» не
должен убирать пункт — он остаётся неутверждённым, лишь помечается отметкой
голоса. Убрал ошибочное actedDecisionIds.add из onVoteFor/onVoteAgainst
(оставлен только тихий рефетч). Скрытие через actedDecisionIds оставлено только
в onAuthorizeDecision — там пункт исполняется и должен уйти из повестки без
возврата от отстающего поллинга.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Две проблемы при голосовании/утверждении в повестке совета:
1. Моргание всей страницы: QuestionsTable подменял список тремя
скелетонами при любом loading=true, а пост-экшен loadDecisions вызывался
без hidden → loading=true → список «моргал» в скелетоны и обратно.
Скелетоны теперь показываются только на первой загрузке (loading &&
!decisions.length), а пост-экшен рефетчи переведены в тихий режим (hidden).
2. «Исчез → вернулся → исчез»: повестка на бэкенде уже не отдаёт пункт, по
которому проголосовал/утвердил, но данные из блокчейна доходят с задержкой,
и поллинг успевал вернуть отработанный пункт. Добавлен локальный набор
actedDecisionIds — проголосованный/утверждённый пункт прячется сразу и не
возвращается, что бы ни подтянул отстающий рефетч. Просто, без оптимистичных
merge-оверлеев.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Две независимые проблемы при выдаче разрешения на уведомления:
1. Фриз приложения: shouldShowDialog был computed с побочным эффектом —
внутри геттера вызывался updateSupport(), присваивавший store.support
новый объект на каждое чтение. После выдачи разрешения это давало каскад
инвалидаций/ре-рендеров, забивавший главный поток (роутер менял URL, DOM
«застывал»). Геттер сделан чистым, updateSupport() вынесен в showDialog().
2. Красная ошибка «Service Worker не готов в течение 5 секунд»: в dev без PWA
SW намеренно не регистрируется, поэтому navigator.serviceWorker.ready
никогда не резолвится. Теперь сначала проверяем getRegistrations() — при
отсутствии SW выходим сразу и тихо (warn вместо error), а subscribe()
возвращает false без FailAlert. Push при недоступности SW полностью
изолирован и не влияет на работу приложения.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Кнопки взноса и возврата на столе пайщика недоступны, пока пайщик не
подписал главное соглашение цифрового кошелька (type='wallet') — как и
сама карточка кошелька, которая не появляется без подписанного соглашения.
Геттер isWalletAgreementSigned в сторе Wallet читает тот же список
соглашений пайщика, что и RequireAgreements.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
«(опционально)» в заголовок + пояснение в описании: если есть пайщики —
импортируйте CSV, если нет — двигайтесь дальше. Чтобы шаг не воспринимался
обязательным.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
readonly outlined q-input рендерится с dashed-рамкой (дефолт Quasar) и
обрезал значение (…UJR4) — выпадало из канона и выглядело плоско.
Сделал ключ surface-2 панелью: eyebrow-лейбл + copy-иконка в шапке,
mono-значение целиком (word-break) ниже. Даёт вес и убирает «ёлочку».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
matrixRoomId — это одновременно идентичность и capability: зная его, можно
попытаться войти в комнату. Раздавая его на фронт (страница «Комнаты секретаря»
и список транскрипций отдавали его в каждой строке), мы по сути публиковали
handle для входа в приватные комнаты любому, кто видит раздел. Приватность тогда
теряет смысл.
Комнаты секретаря:
- ChatcoopSecretaryRoom и RemoveSecretaryRoomInput переведены с matrixRoomId на
внутренний opaque id реестра (DB-ключ, по нему войти в Matrix-комнату нельзя);
- удаление резолвится по id → matrixRoomId на бэкенде; мутация больше не
возвращает matrixRoomId;
- фронт: row-key/удаление по id, убран показ matrixRoomId в таблице;
- попутно фикс латентного бага из PR #34: parseKind не знал про 'secretary' и
мапил такие комнаты в capital_project — из-за чего editable=false и удаление
падало с «удалять можно только комнаты секретаря».
Транскрипции:
- CallTranscription больше не отдаёт matrixRoomId (на фронте он не отображался,
но уходил в payload по всем комнатам, включая приватные). Остаётся roomId
(LiveKit room name — внутренний, не Matrix-handle);
- input-фильтр GetTranscriptionsInput.matrixRoomId сохранён: это вход, не утечка,
и им пользуется blago-cli;
- blago-cli: рендер транскрипций больше не зависит от tr.matrixRoomId (строка
«Matrix room» убрана, иначе pull и restore рассогласовались бы — restore не
имеет контекста комнаты). Заодно поправлен fallback курсоров.
Вне scope намеренно: chatcoopListNonProjectCommunicationRooms и per-room запросы
оставлены с matrixRoomId — их зовёт только blago-cli (доверенный инструмент
председателя), на фронт они не уходят, и matrixRoomId там — операционный ключ
синхронизации.
logger.ts: при CONTROLLER_SCHEMA_GEN не подключаем файловые транспорты winston
(codegen рядом с работающим контейнером падал на EACCES в root-овый logs/).
schema.gql + zeus (sdk и controller) перегенерированы и закоммичены — иначе CI
typecheck собирает sdk против старого zeus и падает.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Прошлый вариант разъезжался: «Скопировать» висел сиротой справа, провалы
между блоками, узкая потерянная кнопка, карточка 560px — разрежено.
Переделал по канону:
- копирование ключа — иконкой content_copy в append самого поля (+tooltip)
- кнопка «Установить ключ» — full-width (block), как «Войти» в LoginForm
- ширина карточки 480 как у остальных auth-карточек
- чекбокс слева, плотный ритм без лишних margin (reserve-hint-space)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Голый «Потребительский Кооператив» в примере провоцировал вводить просто
ОПФ без расширения. Дал полный пример «...Социального Комплекса» во всех
трёх падежах — чтобы вводили расширенную ОПФ+, а не базовую форму.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Это не переменные, а постоянные параметры, которыми настраивается фабрика
документов кооператива. Переименовал label и intro шага.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Микроотступ стал лишним и снова раздвигал поля слишком широко — откатываю.
Разделение полей обеспечивает сам reserve-hint-space, дополнительный
глобальный отступ не нужен.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Постоянный hint под полем занимал место и отвлекал. Перенёс примеры
наименований ОПФ+ в placeholder (видны только в пустом поле), убрал hint.
Добавил reserve-hint-space всем q-input формы — строка под ошибку
резервируется без текста, валидация не вызывает layout shift.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Без зазора текст hint/error верхнего поля почти касался границы нижнего.
Вернул маленький отступ, но системно и один раз — глобально в quasar-canon:
margin-bottom: var(--p-1) только полям .q-field--with-bottom (те, что
резервируют нижнюю строку). Работает везде (BaseForm, формы установки,
регистрация), в стеках не складывается в дыру, инлайновые поля не трогает.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
reserve-hint-space у q-input/BaseInput уже даёт ~24px снизу под error/hint;
дополнительный gap в стеках полей складывался с ним → избыточное расстояние.
Убрал gap (канон BaseForm__body):
- CreateOrganizationDataForm / IndividualDataForm (.user-data-stack)
- SetVariablesForm (.vars-section__fields)
- RequestKeyForm (.request-key)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Канон-плотность полей (dense) была только в BaseInput; общие формы
CreateOrganizationDataForm и IndividualDataForm + поля шагов установки
рендерили крупные не-dense q-input. Добавил dense:
- CreateOrganizationDataForm (22 поля) — данные организации
- IndividualDataForm (6 полей) — члены совета
- email-инпуты SetInitForm/SetSovietForm и поля SetVariablesForm
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- InstallCooperativePage: VerticalStepper вместо q-stepper, canon-панель
с accent-стрипом, экран завершения в каноне (без градиентов/shimmer)
- RequestKeyForm: BaseInput (mono) + BaseButton
- SetInitForm: canon info-нотки, кнопки Назад/Далее на BaseButton
- SetSovietForm: canon-карточки членов совета вместо q-card/q-badge
- SetVariablesForm: canon-секции, q-input outlined с правилами, BaseButton
- Invite: осмысленное тело AuthCard при отсутствии токена приглашения
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
.extension-page уже внутри .catalog-shell__content с padding var(--p-6);
собственный padding давал двойной зазор от шапки до кнопки «Назад».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Весь контент страницы расширения обёрнут в канон-панель (surface+border),
больше не лежит на голом фоне
- В режиме настроек добавлена кнопка «Отменить» (CancelButton) рядом с
«Сохранить» — явный выход без сохранения; кнопка «Назад» страницы тоже работает
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Кнопка «Назад» убрана из шапки (useBackButton снят), добавлена канон-кнопка
под шапкой на самой странице
- Логотип AutoAvatar центрирован в колонке
- DesktopsList: центрированный inset-блок (выделяется), а не плоский список
- ext-actions toggle: «включено» и «удалить» снова в одной строке
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Без delay nodemon реагировал на каждое fs-событие пачки (правки/сборка/git на хосте) десятками рестартов в секунду; pstree.remy без `ps` в образе виснет на обходе /proc (накапливались зомби-сканеры), холодный старт ts-node прерывался, порт 2998 не занимался — бэкенд молча лежал. delay:2000 схлопывает пачку в один рестарт, signal:SIGTERM даёт чистое завершение дочернего процесса.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Триггер ринг-анимации перенесён с hover самого SVG на hover карточки
(.app-card:hover :deep(.ring-seg)).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AutoAvatar получил режим animated: инлайн-SVG вместо <img>, каждый
сегмент-дуга обёрнут в .ring-seg. По hover логотипа сегменты на ~1.3с
расходятся — прямые и зеркальные (matrix-flip) от одного поворота идут
в разные стороны, со стаггером по индексу. CoopCard не затронут (img/blob).
Уважает prefers-reduced-motion.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Статус идёт сразу под заголовком (динамично) — без дыры под однострочными
названиями. Обрезку в 2 строки оставили, чтобы длинный заголовок не ломал сетку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заголовок резервирует 2 строки (clamp+min-height), аватар по верху —
статус и описание на всех карточках начинаются на одной линии, не прыгают.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Верхний ряд: логотип + (заголовок, под ним статус); описание во всю
ширину ниже; футер «Подробнее» прижат к низу для выравнивания карточек.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Палитра колец → мягкие приглушённые тона + CSS saturate(.5)/opacity .85,
чтобы знак читался как тихая текстура, а не неоновое пятно.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- AutoAvatar параметризован: props size/radius/background/ringColor
с обратно-совместимыми дефолтами (CoopCard не затронут)
- ExtensionCard: буква-монограмма заменена на уникальный rings-логотип,
seed = имя расширения; цвет кольца детерминирован по seed из палитры
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
extensionRepository.update заменяет config JSONB целиком; chairman/powerup/meet-tracker писали захваченный при initialize снимок поверх свежих данных. Теперь read-modify-write по актуальному config из БД с наложением только владомых полей.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- PaymentProviderForm: убраны вложенные q-gutter (кривой левый отступ
селекта), standout=bg-teal → outlined, text-grey-7 → токен; чистый
канон-столбец (селект max-width 480, hint ink-2, действия в ряд, no-caps).
- ChangeContacts: текст уточнён — контакты видны всем (раздел «Контакты
кооператива» пайщикам + подвал сайта незарегистрированным), не только
незарегистрированным.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Кооперативные участки, Регистрационные взносы, Ключ кооператива,
Провайдер платежей, Контакты кооператива:
- убран дублирующий заголовок страницы (есть крошка в шапке);
- описание перенесено с голого фона на канон-поверхность .banner;
- page-shell/hero/surface-card с хардкод-px → канон-отступы и токены;
- q-input/q-select standout=bg-teal → outlined color=primary.
Также удалён лишний хвост текста на Стартовых страницах.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заголовок страницы уже показан крошкой в шапке — повтор h2 убран.
Пояснительный текст вынесен с голого фона на канон-поверхность (.banner
с инфо-иконкой). ApprovalsPage не трогаем — там заголовка страницы нет.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ApprovalsPage: канон-отступы страницы, фильтр-селект teal standout → outlined.
- SystemSettingsPage: hero-card/surface-card с хардкод-px → канон page-head
(h2 + sub на токенах) + q-card(flat); канон-отступы.
- MembersPage: тот же канон-паттерн вместо hero-card с хардкодами.
- DefaultPagesForm: 4× q-select standout=bg-teal → outlined color=primary.
Виджеты таблиц/состава совета без экзотики — канон-тема через quasar-canon.css.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Шаблон двухкорневой (кнопка + Teleport затемнения), class из родителя
(LeftDrawerMenu) не наследовался автоматически. inheritAttrs:false +
v-bind=$attrs на корневую кнопку — атрибуты направлены явно.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Подписи секций «Вопрос на повестке»/«Проект решения» переведены в
надстрочные метки (uppercase, трекинг, ink-3) — отдельный ярус ниже
заголовка окна, чтобы не казаться конкурирующим заголовком рядом с
крупным заголовком самого документа. Текст вопроса — читаемый body.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ConnectPage: добавлены канон-отступы страницы (var(--p-6)/var(--p-4)) —
класс .padding в проекте не определён, контент липнул к краям.
- OnboardingStepsCard: убран q-card (двойной паддинг), текст-интро
растянут на полную ширину (снят max-width 70ch), вокруг кнопки шага
убрана лишняя рамка (.step__content → .step__action, только отступ).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- OnboardingStepsCard: под заголовком «Адаптируйте кооператив…» добавлен
поясняющий текст (что происходит дальше: решения совета в электронной
форме, вступление в «Восход», импорт пайщиков, общее собрание) + плашка
срока адаптации с отсчётом.
- Чек-лист шагов переведён на канон-вертикальный степпер (.stepper--v):
пройденные — галочка/заливка primary, текущий — подсветка номера,
действия — канон BaseButton; agenda/import/meet типы сохранены.
- Диалог «Предложение повестки» (объявить собрание совета) переведён
с ModalBase на канон BaseDialog + BaseButton.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CommonHeader: крошка строится из route.matched (путь внутри стола),
напр. «Отчётность › Календарь»; корень стола не выводится; плоские
страницы дают одну крошку как прежде; pageTitleOverride сохранён.
- CoopWalletsPage: переводы временно скрыты под флагом transferEnabled=false
(открывались с L3-кошельков пайщиков; вернём для кооп-кошельков позже).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- editor-container/action-panel/backdrop/validation-badge/mark-hint: hex и rgba → --p-* токены
- text-grey-8 → t-muted; box-shadow/overlay через токены с fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SecondLevelTabs на глобальных .tabbar/.tab — sub-навигация между топбаром и контентом
- WalletsPage/DocumentsPage shell-страницы: вкладки в .tabbar вместо RouteMenuButton в топбаре
- TransferWalletsButton: канон header-кнопка (q-btn primary)
- WalletTransferDialog: на BaseDialog + BaseButton, токены вместо rgba
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ZodForm: убран standout="bg-teal text-white" с динамического поля —
QInput/QSelect теперь канон (outlined+dense+color=primary+reserve-hint-space),
как BaseInput. SCSS на канон-токены (вместо неопределённого --q-primary-rgb).
ExtensionSettings: снят заголовок «Настройки» внутри карточки — он дублировал
«Настройки аренды» в шапке.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Канон-инпут вместо q-input: BaseInput расширен поддержкой type="date"
и clearable (+ emit clear) — это нужно для фильтра по датам. Фильтры
в ExtensionLogsList переведены на BaseInput.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ExtensionSettings: EmptyState + BaseCard вокруг ZodForm вместо самописных
header/empty-state. ExtensionLogsList (используется только powerup):
q-table → список канон-карточек с фильтром по датам, скелетоном,
«Загрузить ещё» и EmptyState; слот #log-item и API сохранены.
Канон-падинги страниц настроек/лога аренды.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Монитор ресурсов: баннер-предупреждение на всю ширину сверху, ряд
«Как это работает» (2/3) + кошелёк AXON (1/3), ряд CPU/NET/RAM.
Убран 3D-флип карт (он же «прыгал») — детали раскрываются inline.
Все виджеты/страницы/лог сведены к канон-токенам surface/line/ink.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Реестр платежей: кнопки действий больше не block (не заполняют ячейку
от левого края, не липнут к бейджу статуса). Стопка inline-flex,
равная ширина кнопок (align-items:stretch), прижата к правому краю
колонки (col-action 168px, text-align:right) — слева остаётся воздух.
- Реестр документов: col-date 116→156px — дата «26.05.2026 08:25» больше
не налезает на заголовок документа; min-width 720→780.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Bank.vue: панель платежа max-width 380px по центру — длинное «Назначение»
больше не раздувает диалог «Совершите взнос» до max-content; значение
переносится по строкам (overflow-wrap: anywhere).
- Кнопки статуса платежа SetOrderPaid/RefundedStatusButton → BaseButton
(primary/danger, block), подписи «Подтвердить»/«Отклонить»; диалог
вынесен из-под кнопки.
- Реестр платежей: кнопки действий друг под другом (.cell-actions),
колонка действий 156px, дата без переноса (.col-date 132px), min-width
таблицы уменьшен.
- Реестр документов: сужены колонки подписей (220→150), действий
(110→56), id/дата; min-width 880→720 — освобождено место под
наименование, таблица не вылазит за страницу.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Таблицы документов и платежей уже на канон-токенах и самообрамлены
(.table-wrap). Не хватало полей страницы как в реестре пайщиков:
- PaymentsPage рендерил виджет голым, впритык к краям → q-page + 24/16px.
- ListOfDocumentsPage: q-page.padding (16px) → канон-24px.
- Убрана лишняя .row.justify-center обёртка в ListOfDocumentsWidget
(.col-12 и так во всю ширину).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AddUserDialog перестроен в канон-визард по образцу CreateMeetForm:
шапка-bar (surface+line) вместо bg-gradient-dark, VerticalStepper из трёх
шагов — Электронная почта → Тип и данные → Вступительный взнос, BaseButton
в подвале, outlined-инпуты. UserDataForm переиспользован целиком (общий,
не тронут). Опция начисления взноса — канон-блок вместо q-item/q-card.
ParticipantsImportDialog: добавлено описание-интро (раньше отсутствовало),
ModalBase/bg-gradient-dark заменён на канон-bar, выбор типа аккаунтов через
BaseRadioCard, dropzone на токенах вместо хардкод-цветов, секции и действия
в канон-обёртках, BaseButton вместо цветных q-btn. Логика парсинга/импорта
и таблицы превью/результатов сохранены.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Страница получила канон-отступ (--p-6 / --p-4 на мобилке), таблица обёрнута
в обрамлённую surface-карточку (--p-line, --p-r-lg) вместо edge-to-edge.
Развёрнутые данные пайщика — спокойная вложенная панель на --p-surface-2
с отступами --p-5/--p-6, читаемой шириной формы (640px) и вертикальным
ритмом между полями. Снят full-height у таблицы (мешал внутри карточки).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Развёрнутая строка пайщика теперь показывает только его данные;
документы пайщика живут в отдельном «Реестре документов» и здесь не дублируются.
ParticipantDetails упрощён (без q-tabs/ListOfDocumentsWidget), tab-логика
вычищена из таблицы и страницы. Мобильная ParticipantCard переведена на канон-токены
(убраны хардкод-цвета, CardStyles и q-badge статуса → disabled-checkbox).
Таблица сохранена, сортировка по дате вступления (DESC) без изменений.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Убран EntityIdBadge. Зелёная плашка-аватар слева теперь несёт сам номер вопроса
(он же Decision ID): клик по плашке копирует id (тултип «Скопировать №»), hover —
инверсия в сплошной teal. Заголовок снова просто текст вопроса.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EntityIdBadge перенесён из отдельной строки под ФИО в начало строки заголовка:
[#id] → сразу текст вопроса. Inline, vertical-align middle. Раньше висел снизу
и пусто растягивал верхнюю строку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EntityIdBadge с id вопроса под заголовком/ФИО: отображает #<id>, по клику копирует
чистый id (copy-on-click). Канон-компонент, клик не раскрывает карточку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
«Истекает через месяц» перенесён из строки под заголовком в постоянную нижнюю
полоску (footer): срок слева, «Утвердить» справа (только председателю). У обычного
пайщика — только срок, узкая панелька.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Срок теперь подписан «Истекает через месяц» (было просто «через месяц» — неясно).
Удалён статус-чип «Вы за/против» — направление голоса уже видно по подсветке кнопок,
бейдж был избыточным. Убраны связанные computeds и стили.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Кнопки голосования прижаты к правому краю строки. «Утвердить» вынесена вниз
отдельной строкой-footer (справа, hairline сверху) — больше не теснит данные.
Срок «через месяц» и статус-чип «Вы за» перенесены влево под заголовок/ФИО.
Клик по строке раскрывает документ, по кнопкам голосования и footer — нет.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Карточка вопроса перестроена в единую горизонтальную строку: иконка + вопрос/ФИО
слева (flex), компактный блок органов управления (голосование + «Утвердить»),
срок + статус-чип и шеврон — справа, друг рядом с другом. Убрана центрированная
vote-зона с пустотой по бокам. Клик по строке раскрывает документ, по органам
управления (@click.stop) — нет. Подсказка «Утвердить» возвращена в tooltip.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Шапка карточки (вопрос/ФИО/срок/статус) кликабельна целиком и раскрывает документ,
справа шеврон-индикатор. Органы управления голосованием — отдельная зона ниже шапки
(сиблинг, не аккордеон): клик по ним голосует/утверждает и документ не раскрывает.
Убрана отдельная кнопка «Документ» — раскрытие теперь по клику на шапку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
QuestionCard: VotingButtons + «Утвердить» вынесены на верхний уровень карточки —
голосовать можно без раскрытия; раскрытие («Документ») открывает только содержимое
документа. Истечение срока остаётся в шапке.
VotingButtons: вернул чек-индикатор «принято советом» (закрашивается при принятии)
вместо иконки verified.
Header-CTA совета (Предложить/Добавить/Импорт): убран push (q-btn--push не попадал
под канон-правило заливки primary → белый шрифт на нетиловом фоне), подписи с заглавной.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
QuestionsTable: q-table → карточный список (скелетоны + EmptyState).
QuestionCard: канон-поверхность, BaseButton «Утвердить», токен-чип статуса
вместо q-badge, локальное состояние раскрытия, без @import CardStyles и хардкод-hex.
VotingButtons: токены pos/neg вместо .text-red/.text-green/#666.
Страница: канон-padding вместо q-card flat.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Названия столов в реестре workspace'ов заведены вразнобой («Стол
благороста», «Стол вычислительных ресурсов» vs «Стол Совета»).
Добавил text-transform: capitalize на подписи в WorkspaceSwitcher
(заголовок текущего стола + пункты выпадающего меню) и WorkspaceMenu
(карусель + диалог выбора) — первая буква каждого слова заглавная,
одинаково для всех и для будущих столов, без правки строк-источников.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
По фидбеку переработана композиция:
- убраны заголовки секций «Контакты»/«Руководство» и per-строчные
иконки телефона/почты/адреса (из-за них значения «прыгали» вправо
относительно ИНН/ОГРН);
- ИНН и ОГРН теперь в одну строку (адаптивная сетка полей);
- председатель поднят наверх к реквизитам, поле названо просто
«Председатель совета»;
- контакты — те же поля, телефон/email как ссылки (tel/mailto), всё на
единой левой кромке; одна тонкая линия делит реквизиты и контакты.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Реквизиты/Руководство больше не разносят подпись и значение к
противоположным краям (на полной ширине это давало пустой провал и
оторванные значения). Все секции теперь как ContactSheet: подпись
сверху, значение под ней, слева, hairline между строками — один ритм
по всей карточке.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ContactsPage: три цветные ColorCard (orange/indigo/teal/blue) заменены
на единую спокойную canon-поверхность с секциями через hairline
(Реквизиты / Контакты / Руководство). Контакты — через canon
ContactSheet (копирование, mailto/tel). Заголовок и строки на токенах
(--p-ink/-2/-3, --p-surface/--p-line/--p-r-lg), полная ширина (padding
24/16px). Убраны rgba-фоны и .q-dark-хаки.
- Удалён неиспользуемый виджет MeetQuorumIndicator (нет импортов;
явка/кворум живут в MeetInfoCard).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ListOfMeetsPage / MeetDetailsPage: убран max-width:960px center — контент
во всю ширину с canon-паддингом (24px/16px), как документы/платежи.
- MeetInfoCard: переписан с токсичного teal на canon — единая calm-поверхность
с тремя секциями через hairline (Даты / Ведущие / Явка и кворум) вместо
трёх вложенных цветных карточек; заголовки ink, проценты ink (не teal);
дубль заголовка «Общее собрание № N» убран (он в шапке). Убраны
var(--q-primary), color-mix, .body--dark, хардкод-rgba.
- MeetDetailsInfo: контейнер с .card-container → canon.
- MeetDetailsActions: q-btn color=primary → canon BaseButton.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CreateMeetForm: повестка на шаге «Проверка» — аккуратные canon-карточки
(Вопрос / Проект решения / Приложения) вместо скомканного списка.
- MeetCompactCard: переписана на canon — нейтральный hover (без
токсичного teal-свечения), ink-заголовок, canon-плитки/иконка;
убраны @extend .card-container, var(--q-primary), .body--dark, color-mix.
- MeetStatusBanner: нейтральный canon-контейнер (surface-2 + line),
цвет статуса несёт иконка; убраны хардкод-rgba и .body--dark.
- MeetCardsList: empty-state на canon EmptyState, skeleton на .skel.
- Детали собрания: кнопка «Назад» убрана из топбара (снят useBackButton),
добавлен canon back-link под шапкой слева; название собрания выводится
в заголовок шапки через новый desktopStore.pageTitleOverride
(приоритет над route.meta.title; транзиентно, чистится при уходе).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Добавлен canon-компонент TableSkeleton (shared/ui/base) — повторяет
структуру .table-wrap/.table с реальными заголовками и мерцающими
плейсхолдерами (.skel) в ячейках. Каркас не дёргается при подгрузке
данных (calm-data, UX-DR2/UX-DR23). Заменил перекрывающий q-spinner
в DocumentsTable и ListOfPaymentsWidget.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- DocumentsTable: наименование берём из meta.title (чистое, без даты/.pdf
суффикса full_title); отдельная колонка Дата (meta.created_at) с сортировкой
по block_num, по умолчанию свежие сверху; колонка Документ — только заголовок.
- ID — копируемый EntityIdBadge (показывает короткий хеш, копирует полный
doc_hash по клику + иконка-affordance).
- Подписи — отдельные BaseBadge на подписанта (новый helper
getSignersListFromDocumentPackage возвращает массив).
- EntityIdBadge канонизирован: токены вместо rgba/--q-accent/.q-dark; добавлен
copyValue (показать одно — скопировать другое).
- SearchHeaderAction: на мобильном — минимальная round-dense иконка без подписи
«Поиск» (label только на десктопе).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Переделка после ревью: q-table давал не-канон вид, дёрганье при раскрытии
(virtual-scroll пересчитывал размеры + colspan не совпадал с числом колонок)
и уродливые мобильные карточки в grid-режиме.
- Статическая канон-таблица .table-wrap/.table (бордер+радиус+surface как
карточка), table-layout:fixed → колонки не разъезжаются при раскрытии.
- Раскрытие строки — CSS-only (expand-row td colspan по числу колонок),
без virtual-scroll → нет дёрганья шапки/колонок.
- Пагинация load-more (.table-foot + BaseButton «Загрузить ещё» + «1–N из M»)
вместо infinite virtual-scroll.
- Мобильный: горизонтальный скролл таблицы (.table-scroll) вместо grid-карточек
PaymentCard/DocumentCard — карточки удалены.
- Статусы платежей — BaseBadge (pos/warn/neg/info/neutral); направление —
иконка + цвет --p-pos/--p-neg; хеш документа — mono.
- Действия платежей (SetOrderPaid/Refunded) скрыты на столе пайщика
(hideActions=true) — они в реестре платежей; download документов сохранён.
- EmptyState + спиннер первой загрузки.
Виджеты общие с админ-контуром: load-more и горизонтальный скролл там тоже
уместнее jittery virtual-scroll.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Когда страница не телепортирует действия, .topbar__actions скрывается
display:none через :has(пустой host), но adjacent-селектор
.topbar__actions + .topbar__right всё равно матчился и обнулял margin-left
правой группы — она уезжала влево к крошке. Возвращаем auto в :has-правиле
(его специфичность выше). Также убрал лид-надпись на странице реквизитов.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ProfilePage (Удостоверение): IdentityPanel-шапка + BaseCard-секции (Учётная
запись/Личные данные/Документы и реквизиты) на DataRow; убран CardStyles,
хардкод rgba и .q-dark.
- PaymentMethods widget (Реквизиты): BaseCard на метод + DataRow + EmptyState
вместо .info-label/.info-value и хардкод-цветов.
- PaymentMethodsPage: кнопка добавления реквизитов переведена с useHeaderActions
store на canon Teleport (#header-actions-host), micro на мобильном.
- AddPaymentButton: триггер под canon micro-паттерн (как Deposit/WithdrawButton).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Симлинк node_modules -> /home/admin/mono-ai-2/node_modules был случайно
закоммичен и ломал pnpm install (ENOTDIR) на любой машине без этого пути.
Правило .gitignore 'node_modules/' (со слешем) ловит только каталог, не
симлинк-файл — добавлено правило 'node_modules' без слеша.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
При удалении publish-docs я снёс не только мёртвый gh-pages-push, но и реальную
публикацию ВТОРОЙ документации (доки mono) через DOCS_DEPLOY_WEBHOOK_URL —
она отдельная от coopenomics и должна уезжать синхронно. Возвращаю её отдельным
lean-джобом trigger-mono-docs (только webhook, без gh-pages и mkdocs-сборки —
приёмник деплоит сам).
Токен GITEA_DISPATCH_TOKEN → DOCS_DISPATCH_TOKEN: Gitea резервирует префикс
GITEA_ для имён секретов, такой секрет не создать.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Дефолтная ветка C9S/coopenomics — master; workflow_dispatch надо слать на
ref=master, иначе Gitea вернёт 404 по ветке.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После переезда на Gitea два downstream-джоба были наследием GitHub:
- publish-docs пушил site/ в gh-pages github.com под secrets.GITHUB_TOKEN
(на Gitea — гитейный токен, невалиден) → Authentication failed. Pages
устарел, доки теперь деплоит C9S/coopenomics. Джоб удалён целиком.
- trigger-coopenomics-docs слал repository_dispatch на github.com через
peter-evans (COOPENOMICS_PAT пуст). Репо переехало в C9S/coopenomics на
Gitea, а Gitea не имеет API для repository_dispatch — только
workflow_dispatch. Заменено на curl к Gitea API workflow_dispatch с
secret GITEA_DISPATCH_TOKEN; целевой workflow получит входы mono_sha/mono_ref.
publish-packages не трогаю — там нужен лишь NPM_TOKEN в секретах (E404 на
scoped-пакетах = отсутствует npm-авторизация).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Крошка: в #crumb рядом с BackButton выводится route.meta.title текущей
страницы (тот же, что подсвечен в меню) — именно для неё действия и
сдвинуты вправо. Длинное название обрезается ellipsis в .topbar__crumb b.
ModalBase: q-bar получил авто-высоту, заголовок переносится (white-space
normal + overflow-wrap), крестик прижат к верху — на узком экране титул
больше не обрезается сверху.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: полноразмерные DepositButton/WithdrawButton в узкой мобильной шапке
раздувались — текст переносился в 2 строки, кнопки вылезали за высоту
topbar. micro-вариант (иконка + tooltip, flat/dense) и предназначен
для слота шапки.
What: Teleport-кнопки получают :micro='isMobile' (useWindowSize, <768px).
Мобильный — компактные иконки; десктоп — полные кнопки с подписью.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
coopback/cooparser/desktop монтируют ./:/app, а node:22-slim без USER пишет
от root — codegen (generate-client, schema.gql, logs, quasar-кэш) кладёт
root-owned файлы в дерево и ломает git checkout (git работает от ant без sudo).
Добавлен user: "1000:1000" + HOME=/tmp, чтобы вывод контейнеров принадлежал
хостовому ant. node_modules/.pnpm-store уже ant:ant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
generate-client пишет копию zeus и в components/controller/zeus — обновляю вместе
со schema.gql, чтобы не оставлять дрифт сгенерированных артефактов.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
generate-schema + generate-client после добавления резолверов SecretaryRooms и
chatcoopListNonProjectCommunicationRooms. Без этого @coopenomics/sdk:build падал
в CI на отсутствующих в zeus типах (ChatcoopSecretaryRoom / CreateSecretaryRoomInput
/ RemoveSecretaryRoomInput / ChatcoopNonProjectCommunicationRoom) — общий build-шаг
обеих джоб typecheck. Локально sdk build (prebuild tsc --noEmit) зелёный.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- entity SecretaryRoom (api/model/store) поверх SDK chatcoopListSecretaryRooms /
chatcoopCreateSecretaryRoom / chatcoopRemoveSecretaryRoom
- SecretaryRoomsPage: список комнат реестра (тип, наличие секретаря, шифрование),
создание комнаты (публичная/приватная) и удаление комнат секретаря
- маршрут chatcoop-secretary-rooms, доступ роли chairman+member
- системные/проектные комнаты — read-only (удаление только у kind secretary)
Затипизируется после generate-schema/generate-client (новые SDK-операции).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Задача 1 (синхронизация в blago):
- roomKind 'secretary' в реестре управляемых Matrix-комнат
- репозиторий: findAll + findNonProjectCommunicationRooms (всё кроме capital_project)
- inter-порт listNonProjectCommunicationRooms + тип InterNonProjectCommunicationRoomRef (kind)
- query chatcoopListNonProjectCommunicationRooms (members/council/secretary) для blago-cli
Задача 2 (комнаты секретаря, backend):
- SecretaryRoomManagementService: создание комнаты (public/private, всегда plaintext,
force-join только секретаря, создатель — модератор) и удаление (kick секретаря + дерегистрация)
- SecretaryRoomsResolver: chatcoopListSecretaryRooms / chatcoopCreateSecretaryRoom /
chatcoopRemoveSecretaryRoom; доступ chairman+member
- matrix-api: inviteUser / kickUser
- конфиг SECRETARY_ROOM_MATRIX
Принцип: секретарь присутствует только в backend-созданных комнатах; force-join в
произвольную чужую комнату запрещён (общий Synapse на все кооперативы).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
registry-1.docker.io на runner'е изредка не резолвится (DNS-таймаут к
127.0.0.53), из-за чего push валит весь релиз уже после успешной сборки
образов. Обёртка dpush() повторяет push до 3 раз с паузой 10с во всех трёх
push-шагах (контракты, base, сервисы).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
clang-9 падал "error while loading shared libraries: libz3.so.4". Пакет cdt
объявляет только libcurl4-gnutls-dev, но бинари тулчейна (objdump -p NEEDED)
требуют libz3.so.4/libtinfo.so.6/libxml2.so.2/libz.so.1. В образе
dicoop/blockchain они были из сборки исходников, из .deb не тянутся —
ставим libz3-4 libtinfo6 libxml2 zlib1g явно.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Шаг компиляции контрактов в release.yaml падал под Gitea act_runner:
build-all.sh монтирует $(pwd):/project в sibling-контейнер, но job сам
исполняется в контейнере → хостовый демон не видит путь, /project пуст,
cmake падает "no CMakeLists.txt". Релиз не собирался зелёным (runs #144, #158).
- build_contracts_cdt.sh: чистый cmake/make без docker (общий источник флагов).
- build-all.sh: тонкая docker-обёртка над ним для локальной сборки.
- release.yaml: вместо pull образа + build-all.sh — установка cdt v4.2.0 .deb
в окружение job'а + симлинк /cdt/build → /usr/opt/cdt/4.2.0 (сводит хардкод
toolchain-путь CMakeLists без его правки) + вызов build_contracts_cdt.sh.
Компиляция в самом job-контейнере, без вложенного docker. sudo-агностично —
одинаково на GitHub-VM и Gitea-контейнере.
- Убран избыточный typecheck-гейт из release; typecheck.yaml остаётся PR-гейтом.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Все три reboot-скрипта теперь идентичны, кроме строки pnpm run boot[:extra|:clean]:
- wipe blockchain-data контейнером (docker run alpine rm) вместо sudo rm —
работает без sudo на любой ноде независимо от владельца данных (на Pi нет
passwordless sudo; на проде nodeos пишет данные под root). Единый способ с reboot.sh.
- monoredis в down -v и up -d — без него coopback падает на старте
(getaddrinfo EAI_AGAIN monoredis → nodemon crash), и провайдер не получает
org-данные partner1.
- coopback через up -d --force-recreate (перечитывает env), как в reboot.sh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- booter.ts: installExtraData (регистрация partner1 с auto-approve → триггер аренды
провайдером) выполняется только под EXTRA_RENT=1. boot:extra используется и для
других задач (пересев совета/чейна), где аренда VM не нужна.
- infra.ts: partner1 подписывает wallet-соглашение (wallet::signagree, program_id=1)
при регистрации. Без членства в ЦПП Кошелька provider.performInitialTransfers
(150 AXON на partner1 перед ACTIVE) падает ассертом eosio.token::is_can_transfer
«Получатель не является участником целевой потребительской программы кошелька»,
и инстанс навсегда висит в INSTALL.
- blockchain/index.ts: activateFeature не роняет boot при protocol_feature_exception
(фича уже активна на не-обнулённом чейне) — ловим и продолжаем.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Штатный синк вместо точечной правки: schema.gql приведён в соответствие с
актуальными DTO контроллера (был протухший — накопленный дрейф полей,
не только blocked). zeus перегенерирован из новой схемы; оба
controller/zeus и sdk/src/zeus идентичны.
Результирующие изменения относительно L3-blocked-ветки:
- удалён blocked из Ledger2Wallet/ProgramWallet (цель PR);
- синхронизированы ранее не закоммиченные дрейфы: is_server_init в SystemInit,
enum-значение AWAITING_AUTHORIZATION, актуализация описаний/ролей @Field.
- blocked сохранён в ChartOfAccountsItem (legacy chart-of-accounts).
Проверено локально: sdk typecheck и controller typecheck зелёные.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Кошельки (по фидбэку — переносы выглядят плохо):
- .wallet-programs из grid (repeat auto-fill minmax) → flex-column: карточки
идут списком во всю ширину страницы.
- canon .wallet__title/__sub: возвращён nowrap + ellipsis (откат wrap-фикса);
на широкой строке текст почти всегда влезает, иначе — ellipsis.
- WalletCard + минимум-карточка: нативный tooltip `title` — при наведении
виден полный текст. Убран .wallet--row reset (базовый снова nowrap).
Действия шапки (кнопки взноса/возврата пропали; нужна новая механика):
- Новый canon-механизм: страница телепортирует свои действия в шапку через
<Teleport to="#header-actions-host">. Host — постоянный span с
display:contents в #actions слоте CommonHeader (при loggedIn).
- :has()-правило прячет .topbar__actions, когда внутри только пустой host
(нет ни store-кнопок, ни телепорта) — чтобы не было пустого разделителя.
- WalletPage переведён на Teleport (DepositButton/WithdrawButton),
useHeaderActions store-механизм убран со страницы.
- Старый useHeaderActions оставлен для прочих 10 страниц — мигрируем
и удалим отдельно. DepositButton сам скрыт, пока пайщик не принят
(status !== 'active') — это защита, не баг.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CI Typecheck падал на @coopenomics/sdk:build (tsc): селекторы
programWalletSelector/ledger2WalletSelector и зависимые capital-селекторы
больше не содержат blocked, но zeus-типы (MakeAllFieldsRequired) всё ещё
требовали его как обязательное поле.
- schema.gql: убран `blocked: String!` из типов Ledger2Wallet и ProgramWallet
(точечно, без полной регенерации — она тянет несвязанный дрейф схемы).
- zeus (controller/zeus + sdk/src/zeus): убран blocked из Ledger2Wallet и
ProgramWallet во всех 4 секциях (ValueTypes/ResolverInputTypes/ModelTypes/
GraphQLTypes). blocked в ChartOfAccountsItem (legacy chart-of-accounts) и
статус-комментарии (accepted|blocked) сохранены.
- CoopCard.vue: удалён мёртвый закомментированный блок с wallet.blocked.
Проверено локально: sdk typecheck и controller typecheck зелёные.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Упраздняем субсчёт "заблокировано" на кошельках. Резерв средств под заявку
на возврат паевого теперь выражается переводом на отдельный COOPERATIVE-кошелёк
w.wal.wpend, а не блокировкой внутри w.wal.share. Все движения — только переводы
и сжигание, без BLOCK/UNBLOCK.
Контракты (ledger2/wallet/capital):
- wallets.hpp: новый кошелёк WITHDRAW_PENDING (w.wal.wpend, COOPERATIVE).
- operations.hpp: из WalletOp удалены BLOCK/UNBLOCK/BURN_BLOCKED (числовые
значения ISSUE/TRANSFER/BURN/NONE сохранены для совместимости истории).
Переключены 3 операции возврата: o.wal.wthreq -> TRANSFER share->wpend;
o.wal.wthdec -> TRANSFER wpend->share; o.wal.wthcpl -> BURN wpend.
- walletop.cpp/revert.cpp: удалены кейсы и валидации блокировки.
- migrate(): свёртка blocked->available по ВСЕМ коопам (выполняется автоматически
при деплое; сигнатура без аргументов). Поле blocked в структурах таблиц
оставлено deprecated — физическое удаление = смена layout таблицы на живых
коопах, отдельный cleanup-деплой.
- capital balances/importcontr: больше не читают blocked.
- p.wal.wthdrw.standard.yaml: стандарт приведён к модели "резерв на кошельке".
cooptypes/SDK:
- operations.ts: WalletOp без BLOCK/UNBLOCK/BURN_BLOCKED; 3 операции возврата
переключены; wallets.generated.ts регенерён (добавлен w.wal.wpend).
- selectors: убран blocked из programWalletSelector/ledger2WalletSelector.
Backend (controller): убрано поле blocked из GraphQL DTO ProgramWallet и
Ledger2Wallet. TypeORM-колонка/внутренние интерфейсы оставлены deprecated
(без DB-миграции).
Frontend (desktop): убраны все поля "Заблокировано" — WalletProgramWidget,
WalletWidget, ParticipantWalletsPage, CoopWalletsPage, CapitalWalletsCardsWidget,
ContributorsListWidget, optimistic blocked_delta.
Границы (не в этом PR): legacy soviet::progwallets blockbal/unblockbal и
donor-marketplace; legacy ledger v1 chart-of-accounts "Заблокированные средства";
полное удаление поля blocked из C++-структур таблиц.
Требует CI-шага: regen GraphQL schema + zeus client (generate-schema/-client)
для согласования schema.gql и zeus-типов с удалённым полем blocked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Why: на странице без логина логотип был <img :src='logo.svg'> — img рендерит
SVG в изоляции, не наследует currentColor, поэтому показывался чёрно-белым
и не реагировал на смену темы. В личном кабинете (WorkspaceSwitcher)
тот же logo.svg рендерится inline через v-html в зелёном квадрате
и наследует color (logo.svg на fill:currentColor) — «зелёненький
на зелёном фоне», одинаковый в обеих темах.
What: CommonHeader brand-slot переведён на тот же приём —
`logo.svg?raw` + v-html внутри .app-q-header__logo
(background var(--p-primary-soft), color var(--p-primary), 28px квадрат,
16px svg). Переключение темы больше не требуется — зелёный константен.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: предыдущий фикс `overflow-wrap: anywhere; word-break: break-word`
ломал слова в любом месте даже когда колонка достаточно широкая —
«Минимальный неснижаемый остаток» рендерился по одному слову на строку,
несмотря на ~480px доступной ширины. Реально нужен только wrap по пробелам;
agressive break-word оправдан только для URL-подобных нерасчленяемых строк.
What: убраны `overflow-wrap` и `word-break` из .wallet__title/__sub —
браузер делает естественный wrap по пробелам, длинные заголовки переносятся
только когда не помещаются. `.wallet--row` reset для compact-варианта
оставлен — там по-прежнему single-line+ellipsis.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: canon-стиль .wallet__title и .wallet__sub был с
`white-space: nowrap; overflow: hidden; text-overflow: ellipsis;` — на full-
варианте это резало длинные подписи («Минимальны…», «Возвращается п…»)
даже на просторных экранах, потому что grid-колонка `.wallet__main` сжимается
ради `.wallet__amount` справа. Эта обрезка будет всплывать на любой
длинной строке (метки программ, статусы пайщика, длинные subtitle).
Compact-вариант `.wallet--row` (слот шапки) должен остаться одной строкой.
What:
- .wallet__title/__sub: убран nowrap/ellipsis; добавлено
`overflow-wrap: anywhere; word-break: break-word; hyphens: auto` (для title)
и `overflow-wrap: anywhere; word-break: break-word` (для sub) — длинные
заголовки переносятся на 2+ строки.
- .wallet--row: явно возвращает nowrap+ellipsis (с reset word-break/
overflow-wrap), чтобы compact-ряды в шапке оставались строго одной строкой.
- WalletProgramWidget: карточка минимального неснижаемого остатка
перемещена в начало grid'а (была после программ) — это базовая защита
средств пайщика, логичнее видеть её первой.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why:
- Заголовок «Кошелёк» для program='wallet' путал — в столе пайщика этот
кошелёк семантически главный, и в любых других местах canon (MicroWallet,
WalletCardMini, _dev/ui) он тоже должен называться полно — «Главный
кошелёк». Лучше один canon-default, чем локальный override в каждом
потребителе. Согласовано — переименование canon DEFAULT_TITLES.
- Минимальный неснижаемый остаток — НЕ баланс кошелька, а самостоятельная
сущность пайщика (паевой взнос, возвращается при выходе). Пристегивать
его DataRow-строкой под grid'ом — нелогично, как было и раньше в legacy.
Правильнее — отдельной карточкой в той же сетке кошельков.
What:
- WalletCard.vue: DEFAULT_TITLES.wallet 'Кошелёк' → 'Главный кошелёк'.
Глобально для всех потребителей canon-компонента.
- WalletProgramWidget.vue: убран TITLE_OVERRIDE и DataRow-блок минимального
остатка. Карточка остатка теперь рендерится в общем .wallet-programs
grid'е canon-разметкой `.wallet` с иконкой `savings`, заголовком
«Минимальный неснижаемый остаток», подзаголовком «Возвращается при выходе
из кооператива» и зарезервированной суммой. Нейтральная подсветка иконки
через `.wallet--minimum { --prog-bg, --prog-fg }`, чтобы визуально
отличалась от программ, но встала в общую сетку.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: на старом WalletWidget пайщик видел свой минимальный неснижаемый остаток
(паевой взнос, возвращается при выходе из кооператива) — самостоятельная
сущность пайщика, не баланс кошелька. При переходе на canon я её упустил.
Канон-default WalletCard 'wallet' = «Кошелёк» — для стола пайщика этот
кошелёк семантически является главным (свободный остаток ЦК), поэтому
локально перекрываем заголовок на «Главный кошелёк».
What:
- TITLE_OVERRIDE['wallet'] = 'Главный кошелёк' — локальное перекрытие
заголовка только в этом widget'е, canon DEFAULT_TITLES не трогаем
(другие потребители WalletCard могут использовать общий «Кошелёк»).
- DataRow «Минимальный неснижаемый остаток» под grid'ом программ
(только когда session.participantAccount.minimum_amount > 0)
с hint «Возвращается пайщику при выходе из кооператива».
- Источник остатка — session.participantAccount?.minimum_amount,
как и в legacy WalletWidget.
Note: locked-line уже рендерится самим canon WalletCard, когда
locked-balance > 0 (logic: hasBlocked ? blocked.amount : undefined
в WalletProgramWidget). Отдельная разметка под «Заблокировано» не нужна.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: type predicate `e is CanonProgramEntry` не сходился — optional `locked?: string`
в interface vs required `locked: string | undefined` в литерале map'а. TS считает
эти типы несовместимыми для predicate, хотя они эквивалентны при присваивании.
What: переход на `flatMap<CanonProgramEntry>` с возвратом `[]` для исключаемых
программ. Predicate не нужен, generic flatMap даёт точный тип результата.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: первая страница стола пайщика (default route 'wallet') использовала legacy
обвязку — CardStyles import, scoped SCSS с .body--dark и hardcoded rgba цветами,
ColorCard с произвольным цветом по индексу. Канон уже знает программы платформы
(blagorost/wallet/generator) через WalletCard + токены --prog-*; на нём и строим.
What:
- WalletPage.vue: убран import 'src/shared/ui/CardStyles', scoped SCSS на
токенах --p-6/--p-4; вырезана легаси-карточка «Минимальный остаток»
(она не отображалась — не было разметки в template). useHeaderActions
для Deposit/Withdraw оставлен — канон поддерживает actions через #actions slot.
- WalletProgramWidget.vue: переписан на canon WalletCard + EmptyState.
Фильтр на канон-набор программ через ZEUS_TO_CANON
(MAIN→wallet, BLAGOROST→blagorost, GENERATOR→generator);
MARKETPLACE и прочие исключены — это не из основной тройки платформы
и должны рендериться отдельным виджетом.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Why: на главную без логина нужно показывать бренд кооператива (логотип + имя)
рядом с глобальными действиями шапки; раньше выводилось только текстовое
название через title-проп без логотипа.
What:
- AppHeader.vue: опциональный slot #brand перед .topbar__crumb;
hasBrand computed по slots.brand. Когда slot заполнен — крошка не рендерится.
- components.css: стили .topbar__brand (desktop + .topbar--mobile вариант)
с canon-токенами (--p-fs-body, --p-ink, --p-fs-meta).
- CommonHeader.vue: на !loggedIn заполняет #brand src/assets/logo.svg
+ <b>{coopTitle}</b>; передача title-пропа убрана.
- default.vue: .fixed-top-right { top: 51px } → top: var(--p-topbar-h)
(canon 56px) — выравнивание FAB под точную высоту шапки.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Существующая модель cmdk в проекте (entities/CmdkMenu/model/store.ts) —
иерархия рабочих столов и их страниц. Активный стол sticky сверху с
бейджем «Активный», без запроса показывается иерархия (стол + indented
страницы), с запросом — плоский список со столом-префиксом у каждой
страницы; стол отдельной строкой появляется только если запрос явно
начинается с его имени или содержит «стол»/«workspace».
Переписал canon CommandPalette под эту модель:
- Props: `workspaces: CommandPaletteWorkspace[]` вместо
`commands: CommandItem[]`. Каждый workspace = `{ name, title, icon,
isActive?, pages: CommandPalettePage[] }`. Page = `{ name, title,
icon?, shortcut? }`.
- Emits: `select-workspace(name)`, `select-page(workspaceName, pageName)`
— props-only, навигация и filtering по ролям/conditions остаются
заботой connected-обёртки (миграция legacy CmdkMenu — отдельная story).
- Sticky-баннер активного стола, plus accent-soft фон + outline-обводка
на selected, ↑↓ работает плоско поверх иерархии (стол → страницы → стол
→ страницы).
Mock-data в /_dev/ui/index.vue обновлён: три стола (Председатель/Пайщик/
Отчётность) со своими страницами вместо плоского списка команд.
Старый widgets/Desktop/CmdkMenu пока живёт параллельно — переключим в
ходе миграции Wave 3.
Реализованы три props-only доменных компонента из E11:
- NotificationCenter — panel-content для popover в шапке: группировка
notifications по category (system/financial/voting/message), unread-bullet
через BaseBadge, кнопка «Прочитать все», EmptyState и «Показать все».
Relative-date форматирование с русским склонением.
- CommandPalette — ⌘K/Ctrl+K с fuzzy-поиском, секции recent/pages/actions,
↑↓ навигация, Enter/Esc обработка. localStorage недавних — в connected
обёртке, компонент props-only.
- DetailsDrawer — side-sheet справа 480px (override через :width), slots
default/actions/footer, Esc и backdrop close, на xs — fullscreen.
Все три зарегистрированы в boot/ui.ts и локально импортированы в /_dev/ui
с mock-data в секциях 36-38.
E11.4 RailUserCard отложен из-за конфликта имён — существующий компонент
имеет другую роль; нужно согласовать канон-нейминг.
Заменил `.full-width.text-center` + flex без выравнивания на flex-column
со `min-height: 360px` — лоадер больше не прижат к левому верху карточки.
Заодно поправил опечатку «подговка» → «Формируем документ».
Корень: я положил символ валюты в #append slot своим <span>, в обход
встроенного prop suffix. Quasar имеет правила позиционирования именно
для родного .q-field__suffix (см. revert d0a8dc0080 от 2026-05-19,
где было решено оставить Quasar дефолт — «нас устраивает»). Мой span
в append-slot не подчинялся этим правилам и сидел в произвольной
позиции относительно цифр.
Фикс:
- :suffix='symbol' — символ валюты идёт нативным механизмом Quasar.
- Удалил .amount-input__symbol класс и template #append вообще.
- Удалил override align-items: center на q-field__control/__append/
__after (тоже мешал, как было показано в d0a8dc0080).
- font-weight 500 на цифрах оставил — нормальный вес поля.
Совет на будущее: использовать встроенные q-input props (suffix,
prefix), а не #append/#prepend slots, если можно — Quasar для них
держит готовое выравнивание, проверенное пользователем.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень: font-weight: 600 + tabular-nums + Quasar dense нативный input
имеют чуть смещённую baseline относительно append-слота, где сидит
RUB. Визуально цифры лежали ниже суффикса.
Фикс:
- font-weight 600 → 500 (нормальный вес поля ввода, без bold-акцента
на цифрах).
- Явный font-size + line-height на нативном input.
- align-items: center на q-field__control / __append / __after, чтобы
суффикс и any after-слот (кнопка «макс») центрировались по высоте
входной полосы.
- align-self: center на самом __symbol — на случай если q-field__append
кто-то переопределит как stretch.
TODO на следующий подход: BaseDocument loader («Формируем документ…»)
выравнивать по центру рамки документа (сейчас стоит сверху).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
[Quasar] boot error: SyntaxError: Unexpected identifier 'as' — runtime-парсер
обрабатывает выражения в pug-template как чистый JS, без TypeScript. Касты
вида `el as HTMLInputElement | null`, `e as InputEvent`, `e as KeyboardEvent`
прямо в атрибутах `:ref` / `@input` / `@keydown` — синтаксическая ошибка во
время бутстрапа Vue, из-за которой /_dev/ui целиком не грузился.
Фикс:
- :ref='(el) => setRef(idx, el)' + функция setRef(idx, el: Element |
ComponentPublicInstance | null) с кастом в TS-скрипте.
- @input='(e) => onInput(idx, e)' + сигнатура onInput(idx, event: Event)
с внутренним кастом target.
- @keydown оставил передавать event «как есть» — KeyboardEvent — никакой
cast не нужен.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Quasar boot-файлы подхватываются только при рестарте dev-сервера. После
коммита 1af0f9c06f глобальные регистрации AmountInput/OtpInput/FilterBar/
FileUploader/VerticalStepper не подтянулись на лету — теги рендерились как
unknown components (пусто внутри секций 31–35 на /_dev/ui).
Фикс: добавил локальные импорты прямо в script setup _dev/ui/index.vue —
тот же паттерн, что у WalletCard, RailUserCard, AuthCard. Глобальная
регистрация в boot/ui.ts остаётся для боевого использования из других
страниц (после следующего рестарта dev'а).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Пять props-only доменных компонентов из shared/ui/domain/:
- AmountInput — денежный ввод с символом валюты, форматированием тысячных,
precision из marketplace asset config, кнопкой «макс» по balance, опциональной
подписью баланса. Tabular-nums, right-align, font-weight 600.
- OtpInput — 6 ячеек с автопереходом фокуса, Backspace откатывает на
предыдущую, paste 6-значного кода распределяется по ячейкам.
Регулярка /^\d$/, состояния error/disabled.
- FilterBar — search (debounce 300мс) + dropdown-фильтры + chip'ы активных
значений с remove + «сбросить всё». v-model для values, v-model:search для
поиска. Активные chip'ы рендерятся под рядом фильтров.
- FileUploader — drag&drop + клик по зоне; валидация accept/maxSize/maxFiles
→ emit error; список загруженных с иконкой типа, именем, размером,
кнопкой ×; слот progress для connected-обёртки.
- VerticalStepper — состояния pending/current/completed/error; completed
кликабельны для возврата назад (опционально); опциональные/disabled шаги;
слот active под телом текущего шага.
Все компоненты — pug, canon-токены --p-*, без store/router/api. Демо-секции
31–35 на /_dev/ui (наш стенд-витрина).
Зарегистрированы в boot/ui.ts и shared/ui/domain/index.ts. ESLint точечно
прошёл, vue-tsc не запускался полностью на desktop (запрет).
Wave 2 / E10 закрыт.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Удалены типы 'txt' и 'image' из DocumentPreviewType + соответствующие
ветки рендера (pre.document-preview__txt и img.document-preview__image)
и стили. В реальных потоках платформы документ кооператива всегда
HTML (рендерится в ShadowHtml внутри BaseDocument) или PDF — plain-
text фрагмент в моноширинном pre не используется и выглядит как
техническая ерунда. Аналогично image.
_dev: удалён previewTxtDemo (мусорный EOSIO chain id из debugging
notes → перешитый в фрагмент протокола → теперь полностью убран
по требованию пользователя). Секция 30 показывает только HTML +
loading + error состояния.
vue-tsc:
- _dev: убрал txId/explorerUrl из signatureSignedDemo — оба поля
удалены из Signature ранее (общего эксплорера нет).
- BaseDocument: canonSignatures.map — нормализую is_valid через
'?? undefined' (бэкенд может вернуть null, canon-компонент ждёт
boolean | undefined).
DocumentPreview txt demo: вместо мусорного 'EOSIO chain id …
dirty window' (мой случайный кусок из debugging notes) — фрагмент
протокола собрания пайщиков ПК «Восход».
ComplexDocument: .col-md-7 → .col-md-10. Это контейнер, в котором
лежит BaseDocument в реальных страницах документов. 7/12 = 58%
ширины было визуально 'приплюснуто'.
Реальный формат подписей кооперативного документа — это не «pending/
signed/rejected» из абстрактного SignatureCard, а IDocumentAggregate с
полями doc_hash + signatures[] (signer_certificate, public_key,
signature, is_valid). Каждая подпись разворачивается в детали.
Что сделано:
- Новый canon-компонент DocumentSignatures (story 9.5) в shared/ui/
domain — props-only, принимает уже резолвнутые signerName и hash-
совпадение, эмитит download/verify. Поверх Quasar — собственный
expand на ref<Set<number>>, чтобы стиль был полностью canon.
- BaseDocument теперь рендерит DocumentSignatures вместо своего
q-card.verify-card + q-list + q-expansion-item на teal/red badges.
Адаптер canonSignatures маппит signer_certificate → ФИО через
getNameFromCertificate, чтобы canon-компонент не знал про сертификаты.
- SignatureCard: удалены поля txId/explorerUrl и ссылка «Открыть в
explorer» — общего эксплорера в платформе нет.
Demo: секция 29 → DocumentSignatures (валидный + с битой подписью),
секция 30 → DocumentPreview (HTML заголовок сокращён, чтобы не
обрывался при узкой колонке).
4 props-only canon-компонента в src/shared/ui/domain/, регистрация
в boot/ui.ts, demo-секции 26-29 в _dev/ui:
- DocumentRow (story 9.1) — строка документа в списке: иконка типа
с tint'ом (pdf neg-soft, docx info-soft, html primary-soft), title,
status через BaseBadge, дата/автор/описание, slot actions, emit open.
- SignatureCard (story 9.3) — подписавший (Avatar+ФИО+AccountBadge),
статус (BaseChip pending/signed/rejected), для signed — хеш в моно-
блоке + ссылка на explorer, для rejected — BaseBanner с причиной.
- ActivityTimeline (story 9.4) — вертикальный таймлайн событий с
цветными иконками по типу (sign/reject/create/update/comment/
transfer), groupByDate группирует по «Сегодня/Вчера/конкретная
дата».
- DocumentPreview (story 9.2) — html (через DOMPurify), pdf через
iframe, image через img, txt через pre. Стейты loading/error.
Все props-only, темо-зависимые значения только через --p-* токены.
Раньше compact-вариант рендерился без обрамления — avatar упирался
в левый край контейнера, визуально «проваливался» из общего стека
full-карточек (Screenshot_2026-05-21_21-51-09).
Compact теперь: padding 8/12px (тоньше чем full 16px), тот же border
+ surface + radius. В одну строку avatar + ФИО + AccountBadge + status,
но визуально это всё-таки карточка, не голая строка.
Body раньше был обычным block-контейнером, а .head и .value-row —
оба display: inline-flex. Без явного block-форматирования inline-flex
элементы становятся inline и рендерились в одну строку. Визуально:
Email⭐ivanov@example.ru, Телефон+7 (903)..., Telegram@ivanov — слипшиеся.
Body теперь display: flex + flex-direction: column, gap canon — 8px
в comfortable, 4px в compact. Head/value-row переведены на обычный
display: flex (вместо inline-flex), gap внутри сохранён.
Прецедент 2026-05-21 — Screenshot_2026-05-21_21-42-16, 21-42-31.
- AccountBadge: copy-кнопка не сжимается (flex-shrink: 0), размер 20×20 + icon 14px — раньше 18×18 + 12px тонула рядом с текстом badge.
- DataRow: column-gap var(--p-3, 12px) → var(--p-5, 20px) + min-label-width 140 → 160px + padding-right на label. Между label и value был визуально слипшийся стык.
- ContactSheet: comfortable margin-top между label и value 2px → 8px (compact 4px). Раньше label «налипал» на значение.
- IdentityPanel compact: переделан в flat flex-row (avatar + ФИО + AccountBadge + status badge) — раньше grid с .body загонял имя и AccountBadge в две строки и avatar «уезжал» от имени. Spec 8.1 требует «только avatar + ФИО + EntityIdBadge в одну строку».
5 props-only canon-компонентов в src/shared/ui/domain/, регистрация
в boot/ui.ts, demo-секции 21-25 в _dev/ui:
- AccountBadge (story 8.5) — on-chain account name в mono-шрифте,
copyable + опциональный explorer link. Назван AccountBadge вместо
EntityIdBadge: имя EntityIdBadge занято под numeric ID пайщика
в capital. Decision зафиксирован в epics.md и planning-artifacts.
- DataRow (story 8.3) — `label: value` пара для реестровых карточек,
mono-режим, copyable, slot value-override, hint.
- ContactSheet (story 8.2) — email/phone/address/tg/web с автоиконками,
кликабельными mailto:/tel:/t.me ссылками, copy и verified-чек.
- IdentityPanel (story 8.1) — Avatar + ФИО + AccountBadge + статус
(active/blocked/pending) + role + actions-slot; compact/full.
- PersonCard (story 8.4) — Avatar + ФИО + role + AccountBadge +
ContactSheet + slot meta; compact/comfortable.
Все props-only: useStore/useRouter/useApi внутри запрещены. Темо-зависимые
значения только через --p-* токены, никаких body--dark селекторов.
q-checkbox по умолчанию красит .q-checkbox__bg в --q-primary, который в
dark теме равен #2dd4bf — на маленьком квадрате 18px этот яркий бирюзовый
смотрится ядовито. Переопределяем заливку filled-состояния на
--p-primary-press (#134e4a light / #14b8a6 dark) — глубже и спокойнее,
чек-символ остаётся белым.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Старое значение --p-accent (#D84315 light / #FF7043 dark) — слишком
насыщенное, выглядит ядовито рядом с тёплой палитрой ink/canvas. Меняем
на тёплую терракоту: #B85C38 (light) / #E89472 (dark) — тот же warm-tone,
но без перевозбуждения красного канала.
.agreement-link (псевдо-ссылки на просмотр документа в согласиях) была
hardcoded в #1c64f2 (синий) light / #ff9f43 (жёлто-оранжевый) dark и не
совпадала со ссылкой «Устав кооператива». Унифицируем: все ссылки внутри
согласий теперь var(--p-accent), визиt-цвет тот же — без фиолета.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseCheckbox block-вариант: убран собственный display:flex + align-items:
flex-start, который перебивал внутренний flex Quasar и сдвигал label вверх
относительно inner. Теперь Quasar сам центрирует inner на первой строке.
Добавлен padding-left: 8px на .q-checkbox__label — больше воздуха между
галочкой и текстом согласия.
ReadStatement: ссылка «Устав кооператива» — --p-primary → --p-accent
(тёплый оранжевый #D84315 / #FF7043 dark) как акцентный цвет палитры.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
UserDataForm/UserDataForm.vue:
- Выбор типа аккаунта: три q-btn(glossy, height:75px) → три BaseRadioCard
с title/description; больше нет teal-«кнопок-плиток»
- Возврат к выбору типа: flat q-btn → BaseButton(ghost) с arrow_back
Sub-forms (Individual / Entrepreneur / Organization + Create*):
- q-input/q-select: standout="bg-teal text-white" → outlined color='primary'
(canon-tone, единый primary-акцент вместо устаревшего teal)
- .q-gutter-sm.q-mt-md → .user-data-stack (flex column, gap токены)
- OrganizationDataForm: кнопка «совпадает» — color='teal' → flat primary
Валидация q-form.validate() и :rules сохранены — это всё ещё q-input
с rules-массивом, только tone и контейнер переехали на canon-токены.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- BaseCheckbox: обёртка q-checkbox с canon-стилями, block-вариант для длинных
согласий (чекбокс прижат к верху, label многострочный)
- BaseRadioCard: карточка-опция с радио-индикатором справа, props title /
description / meta + slot fallback'и; используется для выбора программы
Миграции SignUp:
- ReadStatement: q-checkbox → BaseCheckbox(block); согласия в .agreements-стек
- SelectProgram: q-list+q-radio → список BaseRadioCard
- GenerateAccount: q-input → BaseInput(readonly, mono) с copy в #append;
q-checkbox → BaseCheckbox; автоселект через querySelector('input').select()
вместо ref.select() (BaseInput не пробрасывает внутренний API q-input)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Воздух над заголовком стола внутри WorkspaceSwitcher — заголовок не упирается
в верхнюю границу caption «ПК «ВОСХОД»».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- .rail__top: padding 18px 20px → 16px 8px (унификация боковых отступов)
- WorkspaceSwitcher: убраны :disabled-атрибут и hover:not(:disabled) — у пайщика, которому доступен только один стол, кнопка выглядит как обычно (без not-allowed-cursor, без приглушённого цвета). Меню q-menu просто не открывается через v-if='workspaces.length > 1'. Тихое игнорирование клика вместо явного запрета.
WorkspaceSwitcher показывал ВСЕ workspaceMenus, включая столы выше роли пользователя (например, «Стол председателя» видел рядовой пайщик), что приводило к ошибке access-denied при попытке переключения.
- Добавлена фильтрация по иерархии: chairman ⊇ member ⊇ user. Председатель видит столы любых ролей; член совета — user+member; пайщик — только user. Workspace без meta.roles (или с пустым массивом) — публичный для всех (та же логика, что в legacy WorkspaceMenu, плюс корректное наследование прав).
- Title допускает перенос на 3 строки (line-clamp 3) — «Стол вычислительных ресурсов» теперь умещается полностью
- Расширена кнопка switcher'а (margin сторон 8→4 в LeftDrawerMenu), даёт больше места под текст
WorkspaceSwitcher:
- Brand-строка теперь собирается из vars.short_abbr + «vars.name» (получается «ПК «ВОСХОД»»). Раньше показывал только short_abbr — название кооператива было «потеряно»
- Title допускает перенос на 2 строки (line-clamp 2) — длинные названия типа «Стол вычислительных ресурсов» больше не обрезаются ellipsis'ом с одного слова
- Chevron приклеен к верху строки (align-self start) — корректно при двухстрочном title
- Меню workspace: canon padding (8/12) вокруг q-item, отступ между avatar-section и текстом — иконка и название больше не упираются в края
- Затемнение фона при открытом меню (rgba(9,9,11,0.32) overlay через Teleport в body) — меню больше не сливается с контентом за drawer'ом
Bank.vue (карточка платежа):
- Убрали q-expansion-item (раскрытие вниз внутри карточки выглядело перегружено)
- Кнопка-toggle: «Показать реквизиты» рядом с «Скачать QR». Клик переключает зону под сводкой между QR и списком 9 BaseInput'ов с copy-кнопками
- В режиме реквизитов: кнопки меняются на «Скопировать всё» (primary) + «Показать QR» (secondary)
- QR-canvas использует v-show чтобы сохранять состояние при toggle назад; реквизиты — v-if чтобы не держать BaseInput'ы в DOM
ReadStatement: backend-HTML генерирует с inline-стилями и Quasar text-h* классами, которые перебивали :deep правила. Усилили все ключевые селекторы !important + добавили охват .text-h1/h2/h3/h4/h5/h6 и .text-right (под мета-блок «УТВЕРЖДЕНО…»).
Bank.vue (карточка платежа):
- убрали отдельные surface-карточки вокруг сводки и QR (получалось «карточка-в-карточке-в-карточке»)
- сводка → плоский <dl> со строкой-разделителем снизу (border-bottom var(--p-line))
- QR → просто центрированный canvas без обёртки/фона/рамки; белый цвет внутри canvas — функциональное требование контрастного сканирования, не дизайн-выбор
- detail-expansion → без своего фона, только border-top/bottom
WorkspaceSwitcher (новый widget src/widgets/Desktop/WorkspaceSwitcher/):
- Карточка-кнопка в шапке drawer: иконка кооп + caption «ПК «ВОСХОД»» + bold «Стол совета» (текущий) + chevron
- Клик → q-menu со списком всех workspaceMenus (Стол пайщика, Стол совета, Стол председателя и т.д.); выбор → selectWorkspace + goToDefaultPage
- Подключён через #brand slot AppDrawer'а в LeftDrawerMenu, заменяет дефолтный brand-row
ReadStatement (этап 6.1):
- Backend генерирует HTML документа — локально нормализуем через :deep
- h1 уменьшен с монструозного дефолта до canon h3 (20px), центрирован
- h2/h3, p, strong, ul/ol, table, hr — типографика по var(--p-*)
- мета-блок .approved/.meta-right (УТВЕРЖДЕНО…) — ink-2, мельче, плотнее
- Чекбоксы соглашений снизу не трогали (как просил пользователь)
Bank (этап 6.2 — карточка платежа PayWithProvider):
- QR-код вынесен в фокус: сначала сводка (получатель/сумма/назначение) → QR → действия → реквизиты для ручного перевода скрыты под q-expansion-item
- Полные 9 полей (ИНН/БИК/КПП/корр-счёт/...) переведены на BaseInput(readonly, mono) с copy-кнопкой в #append; раскрываются только при необходимости
- Кнопки «Скачать QR» (primary) и «Скопировать реквизиты» (secondary) — BaseButton
- Удалены: q-btn(push), case-mixed «скопировать реквизиты», стиль #qr через global селектор
- .signature-container: 3px primary border + 3-слойный box-shadow glow → 1px dashed по var(--p-line-2), surface-2 фон, без свечения; на hover лишь окрашивается рамка
- .signature-hint: переехала в центр контейнера, canon ink-2 цвет, убран frosted-chip с blur/shadow — теперь спокойная метка «Оставьте собственноручную подпись в рамке», pointer-events: none пропускает клики на canvas
- min-height снижен 300→220, padding на canon-токены
- SignUp.vue: добавлена обёртка .signup-page (padding + center + min-height) — как у SignInPage; раньше страница была прижата к шапке
- AuthCard maxWidth: 1000 → 720 — историческое 1000 для трёх-колоночной UserDataForm; для остальных шагов выглядит абсурдно широко
- q-stepper: убраны собственный фон/тень/padding (не «карточка в карточке»), линии-разделители переведены на var(--p-line)
- EmailInput: max-width поля 360px — email не должен растягиваться во всю ширину карточки
- canon `shared/ui/domain/AuthCard` переписан на pug
- SignUp.vue переключён со старого «глянцевого» AuthCard на canon (title prop вместо CAPS-заголовка)
- старый `shared/ui/AuthCard` удалён (использовался только в SignUp)
- 7 подэкранов SignUp (EmailInput, SetUserData, SelectProgram, GenerateAccount, SelectBranch, ReadStatement, SignStatement): q-btn → BaseButton (ghost для «назад», primary для «Продолжить»)
- EmailInput: q-input → BaseInput, валидация rules переведена на computed :error
- q-input в GenerateAccount оставлен из-за зависимости от ref.select()
- og-image.png — взят brand-постер ~/blago/production/shared/poster-logo-horizontal.png,
отресайзен до 1000px по ширине и центрирован на полотне 1200×630
с белыми краями по вертикали. Шрифт/композиция родные бренда.
- description / og:description / twitter:description: «Цифровой Кооператив —
система управления хозяйством. Регистрация пайщиков, заказы на поставку
и приобретение имущества, собрания совета, общие собрания пайщиков,
взаимные расчёты на блокчейне, автоматический документооборот и бухбаланс
на основе простой электронной подписи.»
- og-image.png перерисован: «Цифровой Кооператив» / «Система управления
хозяйством» / «на платформе БЛАГО — кооперативной экономики для жизни» /
«Регистрация пайщиков · заказы на поставку и приобретение имущества ·
расчёты на блокчейне».
- description / og:description / twitter:description выровнены под этот
же копирайт. Слово «взаимоотношения с пайщиками» убрано как слишком
узкое — управление хозяйством включает в себя ещё заказы, реестры
и документы.
- src/assets/logo.svg, public/favicon.svg, public/logo.svg — единая SVG
логотипа кооператива (path с «ц» внутри). Скачано с
https://цифровой-кооператив.рф из шапки сайта.
- src/assets/logo.svg: fill -> currentColor чтобы лого внутри
canon .rail__brand наследовал --p-primary (для тёмной темы тоже).
- LeftDrawerMenu подключает лого через slot #brand-icon AppDrawer
(импорт ?raw + v-html). Внутри зелёного квадрата шапки рейла теперь
наш системный знак, а не дефолтный q-icon "dashboard".
- index.html: favicon → /favicon.svg, apple-touch-icon тоже на SVG.
Удалены неработающие ссылки на /favicon.ico и /apple-touch-icon-*.png.
- index.html: добавлены description + расширенные og:* и twitter:*
с конкретным описанием и og-image (1200×630), чтобы ссылки в чатах
выглядели прилично.
- public/og-image.png — превью 1200×630, ImageMagick-сгенерированный
композит из логотипа + текста.
- Удалены неиспользуемые ассеты:
- public/icons/, public/pwa/ — старые favicon/PWA-PNG'и всех размеров;
- src/assets/* — 40 легаси SVG/PNG (anime, blockchain, dacom*, flow*,
header-logo, quasar-logo-vertical, system*, club*, welcome.jpeg и т.д.).
Оставлены только src/assets/pin.svg (используется в Map.vue) и
src/assets/logo.svg (новый).
Pug-template передаёт выражения в Vue compiler как строки; конструкции
вида (entry as RailItem).route не превращаются в JS и падают
SyntaxError в браузере: «Unexpected identifier 'as'».
Касты унесены в <script>: добавлены computed flatItems и
normalizedGroups, шаблон работает с уже типизированными массивами без
inline-TS.
- LeftDrawerMenu: импорт useCmdkStore → useCmdkMenuStore (правильное имя).
Без этого ломался ESM build всего drawer'а: «no exported member
useCmdkStore» — RailUserCard не монтировался, поэтому и chevron свёртки
«не работал», и Найти/Пополнить не реагировали.
- AppDrawer.vue, AppHeader.vue, RailUserCard.vue переведены на pug
(lang="pug"). В проекте везде pug — никакого исторического HTML в
canon-компонентах быть не должно.
- q-drawer width 240 → 248px (совпадает с canon --p-rail-w, кошелёк больше
не уплывает справа из-за обрезки на 8px)
- LeftDrawerMenu: убран весь :deep override на .rail__usercard/.rail__signout,
возвращаем canon margin/padding как есть
- LeftDrawerMenu: добавлен v-model:collapsed на RailUserCard с сохранением
в localStorage — chevron «свернуть/развернуть» снова работает
- LeftDrawerMenu: кнопка Найти теперь дёргает cmdkStore.openDialog() напрямую
(фейковый KeyboardEvent не доходил до глобального обработчика)
- LeftDrawerMenu: «Пополнить» → useDepositDialog().open(); DepositButton +
WithdrawButton рендерятся скрыто как держатели q-dialog (q-portal в body)
- Header.vue и LeftDrawerMenu.vue переписаны на pug (как остальной проект)
- AppDrawer: плоский items оборачивается в .rail__nav, появляется gap 4-8px до cmdk
- cmdk margin 16→8px, чтоб выровнять с rail__nav (8px от стенки)
- LeftDrawerMenu footer: WalletCardMini+LogoutButton → один canon RailUserCard
(avatar+balance в primary-soft + Пополнить + встроенный signout)
- :deep override на .rail__usercard margin 16→12/8 и .rail__signout padding 12/20→12/12
- quasar-canon: body--light/dark + q-layout/q-page-container/q-page красятся
через --p-canvas, чтобы фон страницы соответствовал шапке и рейлу
LeftDrawerMenu:
- legacy `MicroWallet` (ColorCard teal с большой карточкой пайщика + ИП-badge
+ Deposit/Withdraw micro-кнопки) → canon `WalletCardMini` из
widgets/wallet-card-mini (тонкая карточка из shared/ui/domain/WalletCard
compact-variant, читает walletStore сама).
- Убран toggle «свернуть/развернуть нижнюю секцию» — нижняя секция теперь
всегда видна (короткий компактный блок: WalletCardMini + Выйти).
- Удалены неиспользуемые `onMounted`/`ref` импорты, slide-анимация и CSS
toggle-кнопки.
LogoutButton:
- pug + q-item с красной полупрозрачной плашкой → плоский ghost-button в
стиле rail-пункта AppDrawer: прозрачный фон в покое, на hover —
`--p-neg-soft` фон + `--p-neg` текст/иконка.
- Из uppercase «ВЫЙТИ» в title-case «Выйти» (canon — никаких uppercase).
Визуально левый drawer теперь полностью в canon-семье: единый rail с
пунктами + поиском + компактным wallet-балансом + ghost-кнопкой выхода.
Закрывает этап 3 из 5 в волне «приземление DS на реальный layout».
Дальше — SignUp (этап 4) и Invite (этап 5).
widgets/Header/CommonHeader/Header.vue теперь использует canon AppHeader
из shared/ui/layout. Высота шапки 56px (--p-topbar-h), border-bottom
hairline, без shadow и фоновых градиентов.
Слоты:
- #crumb: BackButton (для авторизованных), либо title с названием
кооператива из system.info.vars для гостей/install.
- #actions: headerActions injection через useHeaderActionsReader (всё что
страницы инжектят — Deposit/Withdraw, SettingsDropdown и пр. — рендерим
как было).
- #notifications: NotificationCenter (только loggedIn + isClient).
- #theme: ToogleDarkLight — пока legacy, сохраняет storage + PWA-цвет;
наш canon ThemeToggle их не делает (привяжу позже отдельным эпиком).
- #profile: BaseButton primary для гостей — Регистрация/Вход (в зависимости
от текущего route).
Удалены:
- MainHeader.vue (вся логика scroll arrows + carousel actions group ушла —
canon `.topbar__actions { gap: 8px }` достаточно; overflow-scroll
будет вернут отдельно если потребуется на узких экранах).
- HeaderStyles.scss (стили scroll-arrow и q-toolbar overrides больше
не нужны — canon-разметка через `<header class="topbar">`).
q-header Quasar остаётся wrapper'ом (sticky-poзиционирование в q-layout),
внутри — AppHeader даёт canon-разметку.
Переписан widgets/Desktop/LeftDrawerMenu/LeftDrawerMenu.vue: внутри теперь
canon `AppDrawer` из shared/ui/layout с rail-видом MONO v2 (логотип ПК +
короткое имя кооператива в шапке рейла, ⌘K-поиск, пункты-router-link'и
с иконкой/бэйджем, sticky-footer).
Адаптер store→canon:
- items: desktop.activeSecondLevelRoutes → RailItem[], с прежней
фильтрацией по roles + meta.conditions + meta.hidden (один-к-одному
логика из SecondLevelMenuList).
- activeKey: вычисляется из router.currentRoute с поддержкой группового
паттерна project-* → projects-list.
- @select: router.push({name, params:{coopname}}) или
actionsStore.executeAction(meta.action), закрытие drawer на mobile.
- @cmdk: эмиттим keyboard event ⌘K → CmdkMenu (он смонтирован глобально
в default.vue) подхватывает.
Footer-слот: пока сохранён legacy MicroWallet + LogoutButton + toggle
свернуть/развернуть (заменим на WalletCardMini в этапе 3).
default.vue: q-drawer width 200 → 240 (canon-ширина rail'а).
Что НЕ затронуто этим коммитом: верхняя шапка (legacy MainHeader),
правый drawer для actions, q-footer для гостей. Их меняем отдельными
этапами 2–3.
Корневая причина «оторванной» галочки в ResetKeyForm: я ранее задал в
mono-platform/components.css глобальное `.row { display: flex;
align-items: center; gap: 12px; }`. Quasar внутри `q-checkbox` (а также
q-radio, q-toggle, q-btn-toggle и др.) сам добавляет class="row" на
корень компонента и использует его как inline-flex — мой `gap: 12px`
раздвигал `.q-checkbox__inner` и `.q-checkbox__label` на 12px по всему
приложению.
Решение:
- Переименовать утилиту `.row` → `.u-row` (та же семантика, тот же набор
модификаторов --wrap, --gap-2/4/6). У Quasar теперь свободный .row.
- Обновить все 6 использований в /_dev/ui.
- Откатить предыдущий :deep(.q-checkbox__label) padding-left:4px workaround
в ResetKeyForm — лечил симптом, причина устранена.
Затрагивает только нашу dev-витрину и canon-utility; legacy pug-виджеты
с `.row.justify-center` продолжают работать через стандартный Quasar
flex-grid класс.
Quasar по умолчанию даёт ~12–16px padding на .q-checkbox__label, в
compact-форме сохранения ключа это смотрелось «оторвано». Уменьшаем до 4px
через :deep override на классе .rk-form__confirm.
Три кадра в одной секции: LostKey (ввод email), ResetKeyForm check-mail
(уведомление «письмо отправлено»), ResetKeyForm save-key (демонстрация
сохранения ключа с моковым сгенерированным аккаунтом). Submit на третьем
кадре регенерирует мок-ключ для повторной проверки.
Заменяет ошибочную секцию ChangeKey из предыдущей итерации.
ChangeKey-фича (формы current/new/confirm WIF + успешный диалог) была
ошибочной интерпретацией E7. Реальный flow «сменить ключ» в продукте —
это **двухэтапный ResetKey**:
1. LostKey: пайщик вводит email (он потерял ключ — текущего WIF нет),
бэк присылает письмо со ссылкой `?token=...`.
2. ResetKey: при заходе по ссылке клиент сам генерирует новый ключ
прямо в браузере (через `useCreateUser().generateAccount()`),
показывает приватный ключ readonly с «Скопировать», требует
подтверждения «Я сохранил ключ» и вызывает on-chain
`resetKey({token, public_key})`.
Текущий ключ ввести нельзя (его нет). Новый ключ ввести нельзя (только
генерируется). Никакого подтверждения нового ключа — он один и тот же.
Что сделано:
- Удалена ошибочная `features/User/ChangeKey/`.
- Старый `widgets/Registrator/ResetKey/ui/ResetKey.vue` (pug + сырые
q-input/q-checkbox + кастомные градиентные стили) разбит на:
* `ResetKeyForm.vue` — presentation (props-driven, режимы
`check-mail` / `save-key`), без useRouter/useApi — пригоден для
/_dev/ui витрины.
* `ResetKey.vue` — connected обёртка, читает `route.query.token`,
генерит account через `useCreateUser`, диспатчит `resetKey` через
`useResetKey`, на успех → router.push на signin.
- Новая UI собрана на BaseForm + BaseInput readonly mono + BaseButton +
BaseBanner + AuthCard.
/_dev/ui секция 20 теперь показывает три кадра подряд: LostKey (email
шаг), ResetKey check-mail, ResetKey save-key с моковым сгенерированным
ключом.
FR11, UX-DR9, UX-DR15.
Generic-проброс `<template v-for="(_, slotName) in $slots" #[slotName]>`
рушится в Quasar 2.19 рендере: при наличии scoped-слота #append вылетает
`Cannot read properties of null (reading 'key')` в renderSlot + Vue warns
по `_isVue`/`constructor` свойствам. Заменяем на явный список слотов
(prepend / append / before / after / hint у q-input; +option, selected-item
у q-select) — синтаксис, который Quasar держит корректно.
Кейс всплыл на ChangeKeyForm: #append с q-icon (toggle visibility) ронял
страницу /_dev/ui целиком.
Подключение ChangeKeyForm + ChangeKeySuccessDialog к dev-витрине.
Submit запускает заглушку useChangeKey().changeKey() (≈600 мс), по успеху
открывает диалог с новым WIF. Generate генерирует валидный K1 ключ через
SDK и заполняет оба новых поля. Confirmed сбрасывает форму.
Диалог успеха смены ключа: новый WIF в моноширинном блоке с copy-кнопкой,
BaseDialog с closeOnBackdrop=false, closeOnEscape=false, hideCloseButton=true.
Закрытие только через primary-кнопку «Я сохранил ключ»; до копирования
кнопка показывает guard-баннер вместо немедленного закрытия. По confirmed
эмитим событие — реальный flow подключит router.push на дашборд.
FR11, UX-DR11, UX-DR15. Story 7.2 из epics.md.
Форма смены ключа пайщика на новых компонентах дизайн-системы.
BaseBanner severity=warn с предупреждением о потере, checklist последствий,
поля current/new/confirm WIF моноширинные с toggle visibility, ghost-кнопка
«Сгенерировать» через PrivateKey.generate из @wharfkit/session.
Бизнес-логика — заглушка с TODO на привязку on-chain change-key action
(pattern см. features/User/ResetKey/api) и TODO на OtpInput шаг подтверждения
(приходит в Эпике 10).
FR11, UX-DR9, UX-DR15. Story 7.1 из epics.md.
Bug после rename --p-accent → --p-primary: класс .avatar--accent смотрел
на --p-primary-soft (canon teal), а должен был — на --p-accent-soft
(warm orange по новой семантике). И вариант ink (белый bg + dark text)
визуально выпадал из палитры — крайний xl-аватар на демо-витрине казался
«не пришей к звезде рукав».
Новая семантика AvatarTone:
- neutral (default) — серый surface-3 + ink-1 (нейтральный)
- primary — canon-teal soft + canon-teal text (основной выделение)
- accent — warm-orange soft + warm-orange text (редкое выделение,
например подсветка пайщика по batch_id)
Удалён вариант ink — не использовался в реальном коде.
В dev-витрине xl-аватар: tone="ink" → tone="primary".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Override (padding-top:0 + align-self:center на dense) не отрабатывал —
Quasar в dense держит prefix/suffix по baseline вводимого значения,
и пользователю это устраивает. Убираем неработающий override, чтобы
не мешался в дальнейших правках.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Inline `style="margin:0"` на <p>-описании перебивал мой margin-bottom
через CSS (специфичность inline > selector). Переключаюсь на
`> :first-child + *` margin-top — отступ крепится ко второму ребёнку,
не зависит от inline-стилей первого.
Работает универсально: <p> + <BaseInput>, <p> + <BaseForm>, любая
другая пара «описание + контент».
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. --p-accent (canon token): #D84315 (light) / #FF7043 (dark).
Совпадает с legacy Quasar $accent (warm orange, Material Deep Orange).
Проброшен в --q-accent через CSS var — legacy color="accent",
var(--q-accent), text-accent продолжают работать без изменений,
но теперь следуют за canon-темой (light/dark). Резерв под редкое
выделение: ссылки batch_id и т.п.
2. quasar-canon.css: .q-field--dense .q-field__suffix/prefix —
padding-top:0 + align-self:center. Quasar по умолчанию ставит
padding-top: 24px под floating-label, отчего «RUB» визуально
опускался к низу control'а. Теперь в одну линию со значением.
3. BaseForm: убран gap 12px из .base-form__body — reserve-hint-space
у q-input даёт ~24px снизу под error/hint, дополнительный gap
делал расстояние между инпутами неприятно большим.
4. BaseDialog: убран универсальный gap из body, оставлен margin-bottom
только на первом <p>/.intro — между описанием и первым инпутом
воздух нужен, между остальными детьми reserve-hint-space хватает.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Промахнулся `git add -A` — захватил worktree-symlink node_modules
(он pnpm-shared с основным репо). .gitignore покрывает node_modules/
но симлинк-файл — отдельный путь.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Контекст: после проброса --q-primary = var(--p-primary) (4fddfe2343)
наш токен --p-accent и Quasar primary стали обозначать ОДИН и тот же
canon-teal — два имени для одного цвета это когнитивный шум, и при
росте платформы будет путать. Имя `accent` оставляем свободным под
будущий ВТОРОЙ цвет (warm-orange, для редкого выделения вроде ссылок
по batch_id) — это отдельная задача когда понадобится.
Переименовано везде (109 ссылок в 6 файлах):
- --p-accent → --p-primary
- --p-accent-hover → --p-primary-hover
- --p-accent-press → --p-primary-press
- --p-accent-soft → --p-primary-soft
- --p-accent-line → --p-primary-line
- --p-accent-strong → --p-primary-strong
- --p-ink-on-accent → --p-ink-on-primary
Файлы: tokens.css, quasar-canon.css, components.css, RailUserCard,
AuthCard, _dev/ui (включая user-facing labels палитры «Accent → Primary»
и заголовок секции «Акцент → Основной цвет»).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- tokens.css: проброс canon-палитры в Quasar brand-переменные
(--q-primary/negative/positive/warning/info = соответствующие
--p-* токены). Резолвится lazy → автоматически следует за темой.
Теперь Quasar САМ красит focus q-field, q-btn color="primary",
bg-primary utility-классы в canon-цвета без CSS-overrides;
- quasar-canon.css: убраны border-color overrides на ::before
(default/focus/error) — Quasar управляет цветом через --q-primary
и собственный механизм --focused/--highlighted. Оставлены только
background, text-color и rounded углы (border-radius var(--p-r-sm));
- BaseInput/Select: вернули `dense` (компактный 40px) и явный
`color="primary"` (canon teal через --q-primary).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Между нижним инпутом и footer-кнопками был ~32px (q-card-section
padding-bottom 20 + q-card-actions padding-top 12) — выглядело
растянуто. Сводим к 16px (8+8) — кнопки сидят ближе к контенту.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Регрессия мерцания границы и приплюснутости — следствие борьбы наших
overrides с встроенной Quasar анимацией. Возвращаем дефолт: Quasar
сам решает геометрию (height/padding/border-radius/font), анимации,
opacity ::before и focus-индикацию. Наша задача — только покрасить
в canon-цвета.
Удалено из .q-field--outlined:
- height/padding/border-radius/font-size/font-family overrides;
- transition border-color (Quasar сам делает свой transition);
- opacity:1 !important на ::before (был костыль от мерцания);
- box-shadow:none на --focused/--highlighted;
- display:none на ::after underline;
- override .q-field--dense;
- override .q-field--float .q-field__label (Quasar анимирует label сам);
- focus-shadow / outline reset на native input.
Оставлено (только цвет):
- background: var(--p-surface);
- border-color: line-1 / accent (focus) / neg (error);
- color текста, placeholder, label, prefix/suffix, hint/error.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Quasar при focus анимирует opacity ::before (прячет старую границу
перед показом ::after underline). Так как ::after мы спрятали,
получался видимый gap ~200-400 ms «граница исчезла → появилась
с подсветкой». Фикс: opacity:1 !important на ::before,
transition только по border-color.
- BaseTable.vue: убран неиспользуемый импорт BaseTableColumn.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- BaseInput/BaseSelect: убран `dense` — возвращаемся к дефолтному
outlined Quasar (control ~56px). Floating-label получает достаточно
воздуха, текст значения визуально центрируется без сжатия;
- BaseDialog: `.base-dialog__body` теперь flex-column с gap 16px —
описание/инпуты/таблицы внутри диалога получают единый воздух
без правки потребителя (пример: «Паевой взнос: укажите сумму…» →
отступ до инпута появляется автоматически);
- BaseForm: `.base-form__body` gap 4px → 12px — те же требования
внутри формы;
- WalletCard: `.wallet__metric-val` 19→17px — итоговое значение
для аккуратного баланс-блока FULL.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. Stack-label: BaseInput/BaseSelect → только `dense`. Quasar
стандартно сам корректно держит floating-label в dense+outlined,
без CSS-override метка работает по документации Quasar.
2. Двойная focus-подсветка: убран наш box-shadow var(--p-focus-ring)
поверх Quasar внутренней focus-индикации (виден был как «белый
ринг» на тёмной + наш teal). Теперь только border-color меняется
через ::before (canon-минимализм). Дополнительно `box-shadow: none
!important` на --focused/--highlighted control и `display:none` на
::after underline (на случай dense Quasar variants).
3. Mini-wallet в правом верхнем углу AppHeader: слот #wallet удалён
из API (баланс уже отображается в RailUserCard в drawer'е,
дубль в шапке избыточен). Убран из dev-витрины каркаса.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Регрессия после Quasar wrapper-pivot: метрики кошельков выглядели
крупно по сравнению с заголовком/sub-label, а padding'и были
избыточны для compact-варианта.
- .wallet (FULL): padding 18→14px по верт, иконка 44→40px,
metric-val 22→19px, ccy 13→12px;
- .wallet--row (compact): padding 14→10px, иконка 36→32px,
metric-val 18→16px — выглядит как row в списке, а не как карточка;
- .rail__balance-val (mini-wallet в drawer): 22→18px — теперь не
доминирует над identity-блоком пайщика.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень регрессии: жёсткий height: 40px в .q-field--outlined раздавил
вертикальную разметку q-input (Quasar держит padding-top:24px под
floating-label слот; при сжатии текст значения сдвигается). Те же
эффекты для select.
- BaseInput/BaseSelect → dense + stack-label по умолчанию;
Quasar сам корректно держит 40px в dense, метка статически стоит
над инпутом без floating-механики;
- .q-field--outlined: убран принудительный height; .q-field--dense
получает только padding-x, высоту контролирует Quasar;
- .q-btn: font-weight 500→450, min-height 40→36px, padding 16→14px —
кнопки больше не выглядят «жирно/тяжело»;
- .q-btn--outline: hairline-граница через ::before (Quasar pattern),
без двойной рамки;
- размеры sm/lg/dense пропорционально уменьшены;
- .q-chip: высота 24→26px, padding 10→12px, font-weight 500→450;
+ .q-chip__icon размер 14px (для точечных чипов).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseSelect → q-select (outlined, reserve-hint-space, map-options/emit-value
для совместимости с {value,label} options API).
BaseCard → q-card flat с q-card-section head/body. Variants flat/inset/quiet
override-ят через scoped style.
BaseChip → q-chip с color mapping (variant → primary/positive/negative/
warning/info). Square для canon-look.
BaseBadge → q-badge с тем же color mapping. Dot-вариант через scoped style
(8×8 круг).
BaseBanner → q-banner rounded dense с border-left по variant.
BaseDialog → q-dialog + q-card с size→maxWidth mapping. closeOnBackdrop/
closeOnEscape пробрасываются через :persistent / :no-*-dismiss.
BaseTable → q-table с трансформацией columns в QTableProps['columns'].
Hide-pagination, binary-state-sort. Cell-slots работают через body-cell-{key}.
AuthCard (domain) — был на native canon `.card` div. Переведён на q-card flat
для консистентности. Усиленный shadow + accent-stripe сверху сохранены.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseInput теперь рендерит <q-input outlined reserve-hint-space no-error-icon>
с проброшенными slots. reserve-hint-space от Quasar резервирует место
под error/hint встроенно — раньше делали вручную через NBSP. Все Quasar
features (rules через v-bind \$attrs, mask, ref API, native slots) работают
прозрачно. Props API совместим (modelValue, label, hint, error, prefix,
suffix, mono, readonly, disabled, autocomplete, name, id, type).
BaseButton — q-btn с variant→Quasar mapping:
primary → :unelevated color="primary" (заливка accent)
secondary → :outline (border + surface)
ghost → :flat (transparent)
danger → :flat color="negative" (transparent + neg-cвет)
:no-caps :ripple=false для canon-look. Props API совместим.
BaseForm — q-form-обёртка с сводным error-баннером (q-banner) и slot footer.
banner—neg styling прилетает из quasar-canon.css через .bg-negative-soft.
Глобальная стилизация Quasar в quasar-canon.css даёт canon-look всем
этим компонентам и существующим q-input/q-btn/q-card по всему проекту.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Архитектурный pivot: вместо native HTML5-компонентов под canon-классами
стилизуем Quasar поверх. Причины:
- Сохраняется весь Quasar API из коробки (rules/mask/dense/slots/refs)
- Существующий код (сотни q-input, q-btn, q-card, q-select) автоматом
получает canon-look без правок
- Меньше регрессий — Quasar отвечает за accessibility/keyboard/focus
Файл src/css/mono-platform/quasar-canon.css содержит overrides под canon
для: q-field--outlined (q-input/q-select), q-btn (unelevated/outline/flat),
q-card, q-chip, q-badge, q-banner, q-dialog/q-menu, q-item, q-table,
q-checkbox/q-toggle.
Подключен в quasar.config.cjs css[] после components.css. tokens.css
без изменений — все цвета/радиусы/тени тянутся как переменные.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Истинный источник «прыгалки» AuthCard на hover: глобальное правило
`.card:hover { transform: scale(1.03) }` в src/app/styles/style.css
(и его дубль в pages/Marketplace/MainPage/ui/MainPage.vue, тоже не scoped)
бьёт по любому элементу с классом card. AuthCard имеет корневой
<div class="card auth-card"> — каждый hover дёргает её на 3%.
Поиск показал: класс `card` без модификаторов в templates никто кроме
новых canon-компонентов (BaseCard, AuthCard) не использует. Этот глобал —
мёртвый legacy, удаляем безопасно.
NBSP-фикс layout-shift из предыдущего коммита (2a2d9a786a) остаётся
актуальным — он лечил другую (на уровне поля) проблему.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseInput: контейнер field__message теперь рендерится всегда с min-height
calc(--p-fs-meta * 1.4) — раньше div появлялся/исчезал на каждом keystroke
при валидации (см. LostKey emailError), форма прыгала вверх-вниз.
Когда нет ни ошибки, ни хинта — рендерится NBSP, высота сохраняется.
AuthCard: добавлен box-shadow поверх canon-минималистского .card —
canvas #fafafa и surface #ffffff на светлой почти неразличимы, без тени
карточка выглядит плоской. На тёмной отдельный shadow усилен (поверх
canon `--p-shadow-card`, который оптимизирован под data-cards, не hero).
border-color подкручен с --p-line на --p-line-1 для большей чёткости.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Scope-adjustment: plan'овая Story 6.3 предполагала миграцию SignUp как
второго auth-flow, но Register — multi-step wizard (10+ файлов в
pages/Registrator/SignUp/), который требует VerticalStepper из Эпика 10
и не помещается в Волну 1. Вместо этого мигрируем LostKey — простую
одно-полевую auth-страницу, того же auth-домена.
LostKey (widget): pug → html, старый AuthCard (q-card с shimmer) →
canon AuthCard. q-input → BaseInput с inline email-валидацией;
q-btn → BaseButton; всё обёрнуто в BaseForm с loading/error.
Бизнес-логика (useLostKey.startResetKey + router redirect на resetkey)
сохранена 1:1.
LostKeyPage: footer-кнопка «Назад» через ghost BaseButton в footer-slot
AuthCard; центровка через flexbox.
Не мигрировано в Волне 1 (известный gap):
- SignUp wizard (требует VerticalStepper из E10)
- ResetKey / Invite (на старом AuthCard, мигрируются в Волне 2)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
LoginForm (feature): q-input → BaseInput, q-btn → BaseButton, обёрнут в
BaseForm с управлением loading/error. Бизнес-логика (useLoginUser, redirect,
OpenReplay tracking, NotificationPermissionDialog) сохранена 1:1.
Ошибки входа теперь показываются через сводный error BaseForm
(плюс legacy FailAlert остаётся как fallback).
SignIn (widget): импорт AuthCard переключён на новый canon-композит
(src/shared/ui/domain/AuthCard), убран pug-template и градиентные стили.
Добавлен footer-slot — пробрасывается в AuthCard.
SignInPage: переписан с pug → html, footer-кнопки «Потеряли ключ?» /
«Нет аккаунта?» вынесены в footer-slot AuthCard через BaseButton ghost-sm.
Центровка через flexbox вместо QRow grid.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AuthCard (src/shared/ui/domain/AuthCard) — canon .card + center-aligned
head (title/subtitle/slot head) + body-slot + опциональный footer-slot.
Старый src/shared/ui/AuthCard (q-card с shimmer-градиентом) остаётся
для не-мигрированных страниц (SignUp wizard, ResetKey, Invite).
AuthLayout (src/app/layouts/AuthLayout.vue) — wrapper-обёртка для full-page
auth-страниц: фон var(--p-canvas), центрирование, тогл темы в правом верхнем.
Витрина: dev-секция 19 с примером login-формы (BaseForm + BaseInput ×2 +
BaseButton + footer-ссылки).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WalletCardMini подключает WalletCard к Pinia (useWalletStore.program_wallets) +
System store (info.symbols.root_govern_symbol) + useRouter (клик →
name: 'wallet'). Программный mapping: wallet→MAIN, blagorost→BLAGOROST,
generator→GENERATOR (Zeus.ProgramType). Loading определяется автоматически
по наличию данных в store (или пробрасывается через prop).
Подключено в слот wallet AppHeader на dev-витрине (секция 16 — каркас) +
выделенная секция 18 с тремя программами. На dev-странице покажет
skeleton-loading т.к. loadUserWallet не вызывался; на реальных страницах
после init процесса — настоящий баланс.
Со-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Благорост — капитализация РИД (не «накопления на жильё»), Генератор —
генерация РИД (не «доходы от программы»). Кошелёк остаётся «свободный
остаток». Черновые ярлыки риском попадают в production через копипасту.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Канонический .wallet с компактным (--row) и full-вариантами, программные
акценты через --prog-blagorost / --prog-wallet / --prog-generator (UX-DR20).
Витрина на /_dev/ui секция 17: три программы × оба варианта + loading +
empty + locked-line.
Compact (.wallet--row): 36px icon, 18px sum, 14px padding — для слота шапки.
Full (.wallet): 44px icon, 22px sum, 18px padding — для дашборда.
Connected-обёртка widgets/wallet-card-mini (Story 5.2) делается отдельным
коммитом, требует интеграции с реальным wallet-store и MARKETPLACE_ASSET_CONFIG.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Анимация max-height + opacity при свёртке давала видимый прыжок крупной
суммы баланса (`font-size: 22px` + `display: flex; align-items: baseline`):
во время промежуточных кадров baseline переcчитывался относительно
схлопывающегося контейнера → текст «прыгал в середину» и обратно.
Возвращаемся к canon `display: none`. Resize моментальный, без skipping.
Поведение rail (top опускается, bottom-chevron остаётся через flex-spacer)
работает и без анимации — пользователь это и подтвердил в фидбеке.
Chevron rotation через :deep(.q-icon) и balance-route не трогаем —
они независимы.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. **Палитра системы.** Новая секция «00» в шапке dev-страницы со всеми
основными --p-* токенами:
- Акцент (accent / hover / press)
- Поверхности (canvas / canvas-2 / surface / 2 / 3)
- Текст (ink / ink-on-accent)
- Линии (line / 1 / 2) — текстовый input для rgba
- Статусы (pos / neg / warn / info)
- Программные тинты (blagorost / wallet / generator)
Color-picker для hex-токенов, текстовый ввод для rgba. Live-override
через `document.documentElement.style.setProperty()` — inline-style на
:root перебивает CSS-правила, перекрашивание мгновенное. Подсветка
переопределённых токенов + счётчик в шапке + кнопка «Сбросить» (через
removeProperty). Watch на Quasar Dark — при смене темы пикеры
обновляются на новые resolved-значения.
2. **Chevron в свёрнутом виде — full-width.** Из-за того что для плавной
анимации primary остаётся в DOM (max-height:0 вместо display:none),
у него сохранялся `flex: 1` и он съедал ширину карточки → chevron не
мог растянуться через canon-правило `width: 100%`. В `.is-collapsed`
добавили `flex: 0 0 0` + `width: 0` для primary — chevron теперь
корректно занимает всю ширину.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
По фидбеку:
1. **Chevron не переворачивается.** Canon-правило
`.rail__usercard__collapse svg { transform: rotate(180deg) }` рассчитано
на `<svg>`, а Quasar `<q-icon>` рендерит `<i class="q-icon">`. Прокидываем
transition + rotate через scoped `:deep(.q-icon)` — chevron теперь крутится
при свёртке/развёртке.
2. **«Карточка улетает»** — резкий jump при `display:none` создаёт визуальный
прыжок. Подменяем display:none на `max-height: 0` + `opacity: 0` с
transition'ом. Логика canon-селектора `.is-collapsed` не нарушается,
просто добавлена плавность. Если структурное поведение rail (top
опускается, bottom-chevron остаётся на месте) всё равно неудобно —
перевёрстаем во вторую итерацию.
3. **Клик по балансу → маршрут кошелька.** Новый prop `balanceRoute` —
если задан, блок `.rail__balance` оборачивается в `<router-link :to>`.
Параллельно эмитим `balance-click` — для аналитики или функционального
handler'а. На витрине привязал к `/_dev/ui#wallet` чтобы проверить.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1. `RouteMeta.icon` объявлен обязательным в `src/env.d.ts` — добавили
`icon: 'fa-solid fa-flask'` для dev-роута, иначе vue-tsc валится.
2. `RailUserCard` (`shared/ui/domain/RailUserCard`) — доменный
общеплатформенный компонент мини-кошелька в нижней части drawer'а.
Это будущий E11 (Волна 2), но логично вытащить раньше, чтобы основной
layout можно было заменить целиком уже сейчас.
Реализован по canon строго: rail__usercard с usertop+balance+actions,
collapse-chevron справа с поворотом arrow при свёртке (через
`.is-collapsed` модификатор + canon CSS), отдельный `rail__signout`
блок (выходит вне `.rail__usercard` — рендерится сразу после, в том
же `aside.rail` через footer-slot).
Контракт строго dumb по архитектуре: только props/emits/slots.
- props: name, role, avatarSrc, balance, symbol, balanceLabel,
lockedBalance, lockedLabel, primaryActionLabel, collapsed,
showSignout, signoutLabel
- emits: primary-action, update:collapsed (v-model), signout
- slots: usertop-extra, actions
Coнnected-обёртки (с инжектом store/auth) — на уровне страницы,
не здесь.
3. Витрина `/_dev/ui`:
- Секция 12 (AppDrawer): заменили временный огрызок rail-demo__user
на полноценный `<RailUserCard show-signout>` через footer-slot.
- Секция 16 (Каркас приложения): аналогично — теперь у rail полноценный
footer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
E4 «Layout-каркас» Волны 1: 4 структурные обёртки в `src/shared/ui/layout/`.
По архитектуре — только props/emits/slots, без store/router/api, без знания
о данных. Cтраницы передают menu-items, breadcrumbs, табы и активные ключи
сверху.
Состав:
- `AppDrawer` — canon `.rail`: solid surface, активный пункт soft-accent
пилюлей + 2-px рейл слева. Поддерживает плоский список + секции
(RailSection с eyebrow-меткой), opt-in ⌘K-кнопку, slot `footer` для
user-card / logout.
- `AppHeader` — canon `.topbar`: бургер · crumb · `topbar__actions` ·
`topbar__right`. Поддерживает простой title и multi-level breadcrumb.
Глобальные действия через слоты `notifications`/`theme`/`wallet`/`profile`
или общий `right`.
- `PageHead` — canon `.page-head`: eyebrow + title + subtitle + slot
`actions`. Используется когда страница БЕЗ подстраниц.
- `PageTabs` — canon `.tabbar` + `.tab`: вкладки подстраниц с tab__count,
опциональный hairline-разделитель и `tabbar__actions` справа.
Также в составе коммита:
- Убрали AuthCard из base — он композит (cap + body + head + footer),
переедет в E6 Auth flow рядом с SignUp/ResetKey/Invite.
- BaseForm: gap полей 16→10px (тесные формы как в каноне).
- Витрина `/_dev/ui`: 4 новые секции (12-15) на каждый layout-компонент +
секция 16 «Каркас приложения» — AppDrawer + AppHeader + PageTabs в живой
связке без legacy QLayout/QDrawer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Скрипт извлечения из bundled HTML оставил ` <style>...` в начале и
`</style>` в конце обоих файлов. CSS-парсер ловил `<` как ошибочный
селектор и пропускал блок до ближайшей `}` — съедая весь первый блок
правил.
В tokens.css первым блоком был `:root, [data-theme="light"]` со светлой
палитрой — она целиком игнорировалась. `[data-theme="dark"]` ниже
парсился нормально, поэтому тёмная тема работала, а светлая — нет:
кнопки primary/ghost/danger оставались с transparent background и
сливались с canvas.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Заносим эталонную дизайн-систему в проект — только базовые элементы для
дальнейшей компоновки. Source — MONO Design System.html.
Архитектура (по `_bmad-output/planning-artifacts/architecture.md`):
- `src/css/mono-platform/{tokens.css,components.css}` — canon токены и стили
компонентов 1:1 из эталона; импортируются первыми в quasar.config css[].
- `src/shared/ui/base/<Имя>/{Vue,types,index}` — 14 базовых обёрток (Vue 3 +
TS, native HTML над canon BEM, no Quasar-deps кроме Dark API в ThemeToggle).
FSD-граница: только props/emits/slots, никаких store/router/api.
- `src/boot/ui.ts` — глобальная регистрация 14 базовых компонентов.
- `src/boot/theme.ts` — sync `html[data-theme]` ↔ Quasar Dark.
- Inter + JetBrains Mono в index.html.
14 базовых:
BaseButton, BaseInput, BaseSelect, BaseCard, BaseTable, BaseChip, BaseBadge,
BaseDialog, BaseBanner, BaseForm, EmptyState, Avatar, ThemeToggle, AuthCard.
Витрина `/_dev/ui` (dev-only) — каждая обёртка отдельной секцией над чистым
canon-канвасом, без layout-обёрток. Маршрут смонтирован на корневом уровне,
ВНЕ DynamicLayoutWrapper, чтобы legacy default.vue не перебивал стили.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 05:30:28 +00:00
692 changed files with 31404 additions and 26501 deletions
Канон — `features/Branch/CreateBranch/{api,model}/index.ts`: тип в model `export type IXxxInput = Mutations.X.Y.IInput['data']`; функция в api `function (data: IXxxInput) { ... variables: { data } }`. Не делать `as` cast'ов.
## ⚠️ ДИЗАЙН-КАНОН desktop — ОБЯЗАТЕЛЕН К ИСПОЛЬЗОВАНИЮ
**ПРИ ЛЮБОЙ ВЁРСТКЕ В `components/desktop/` НЕ ВЫДУМЫВАТЬ СТИЛИ.** Всегда сначала свериться с каноном. Не строй гипотез о цветах/радиусах/типографике/паттернах из памяти — иди и читай.
**Полная спецификация канона — в skill `/mono-desktop-canon`** (правила обёрток, цвета/токены, иконки, структура страницы, stop-signals). Здесь — короткая выжимка; при расхождении побеждает skill.
**Источник истины — в самом репозитории** (НЕ внешние HTML/прототипы):
| **Живой эталон** (`/_dev/ui` в dev-сборке) | `components/desktop/src/pages/_dev/ui/index.vue` |
**При сомнении — открыть `tokens.css` и `_dev/ui/index.vue`, смотреть как сделано там.** Внешний `shared/MONO Design System.html` и `auth-prototype/` каноном НЕ являются — это устаревшие прототипы.
**Запреты:**
- Экран собирается из готовых компонентов: `shared/ui/base` (вместо сырых `q-input`/`q-btn`/`q-card`/`q-table`/`q-chip`/`q-dialog`/`q-select`), `shared/ui/domain` (WalletCard, DataRow, DocumentRow, IdentityPanel…), `shared/ui/layout` (PageHead, PageTabs, AppHeader/AppDrawer). Голый Quasar — только где обёртки нет (`q-icon`, `q-toggle`, `q-list`, `q-menu`, `q-tooltip`, `q-tabs`, `q-separator`, `q-spinner`, `q-inner-loading`…). Props обёрток не угадывать — читать `*.types.ts` рядом.
- Цвет — только токены `var(--p-*)` (поверхности `--p-surface*`, текст `--p-ink*`, линии `--p-line*`, акцент `--p-primary`, статусы `--p-pos/neg/warn/info`) либо utility-классы/color-props. Никаких сырых hex/rgb. Темы light/dark переключаются через `[data-theme]` на `<html>` — токены следуют сами.
- Spacing/радиусы/типографика — токены `--p-1..--p-10` (4px…72px), `--p-r-sm/md/lg/xl`, `--p-fs-*`/`--p-lh-*` либо классы `.t-*`. Без хардкод-px.
- Иконки — `q-icon(name='…')` именами Material Icons. **FontAwesome (`fa-*`) запрещён** — заменять на Material-эквивалент попутно.
- Никаких локальных переопределений `.q-btn`/`.q-card`/`.q-notification` в feature-файлах — Quasar-overrides централизованы в `quasar-canon.css`.
**Кейс 2026-05-28:** при сомнении в каноне пошёл искать его во внешнем HTML/auth-prototype вместо репозитория — перевёрстал не туда, переделывал. SoT — репо (`tokens.css` + `_dev/ui`) и skill `/mono-desktop-canon`, не внешние файлы.
## Frontend desktop — English имена
В`components/desktop/` и любом Vue/TS frontend коде **все имена идентификаторов — английские**:
`Staging обновлён: в списке ${stagedPaths.length} путь(ей). Пропущено без изменений относительно индекса: ${skippedUnchanged}; по .blagoignore: ${skippedIgnored}; артефакты только pull (messages/ и meetings/): ${skippedPullOnlyArtifacts}.`,
`Staging обновлён: в списке ${stagedPaths.length} путь(ей). Пропущено без изменений относительно индекса: ${skippedUnchanged}; по .blagoignore: ${skippedIgnored}; артефакты только pull (messages/ и meetings/): ${skippedPullOnlyArtifacts}; не blago-сущности (нет type=project/issue/story во frontmatter): ${skippedNonEntity}.`,
inlineconstexpreosio::nameCOMPLETE_WITHDRAW="o.wal.wthcpl"_n;///< Завершение возврата паевого взноса (Dr 80 / Cr 51, BURN_BLOCKED SHARE_FUND_PAY — деньги уходят из системы, без wallet_to).
inlineconstexpreosio::nameREQUEST_WITHDRAW="o.wal.wthreq"_n;///< Запрос на возврат паевого: BLOCK на SHARE_FUND_PAY (без Dr/Cr).
inlineconstexpreosio::nameDECLINE_WITHDRAW="o.wal.wthdec"_n;///< Отклонение запроса на возврат: UNBLOCK на SHARE_FUND_PAY (без Dr/Cr).
inlineconstexpreosio::nameCOMPLETE_WITHDRAW="o.wal.wthcpl"_n;///< Завершение возврата паевого взноса (Dr 80 / Cr 51, BURNс WITHDRAW_PENDING — деньги уходят из системы, без wallet_to).
inlineconstexpreosio::nameREQUEST_WITHDRAW="o.wal.wthreq"_n;///< Запрос на возврат паевого: TRANSFER SHARE_FUND_PAY → WITHDRAW_PENDING (резерв, без Dr/Cr).
inlineconstexpreosio::nameDECLINE_WITHDRAW="o.wal.wthdec"_n;///< Отклонение запроса на возврат: TRANSFER WITHDRAW_PENDING → SHARE_FUND_PAY (снятие резерва, без Dr/Cr).
// баланса упразднена. Резерв средств под заявку на возврат паевого теперь
// выражается переводом на отдельный кошелёк-резерв `w.wal.wpend` (TRANSFER),
// возврат резерва — обратным TRANSFER, завершение — BURN с резерва. Поле
// `blocked` в таблицах wallets2/userwallets оставлено deprecated (всегда 0
// после ledger2::migrate-свёртки) — физическое удаление поля = небезопасная
// смена layout таблицы на живых коопах, выносится в отдельный cleanup-деплой.
//
// Числовые значения ISSUE/TRANSFER/BURN/NONE СОХРАНЕНЫ (не перенумерованы),
// чтобы исторические op_code в blockchain_actions читались бэкендом без сдвига
// смысла (2 и 3 — бывшие BLOCK/UNBLOCK — больше не выдаются и невалидны на входе).
enumclassWalletOp:uint8_t{
ISSUE=0,///< первичный вход средств на кошелёк wallet_to (wallet_from = empty)
TRANSFER=1,///< перемещение wallet_from → wallet_to (с Dr/Cr ИЛИ без — по парам account_id)
BLOCK=2,///< available-=amount, blocked+=amount на wallet_from
UNBLOCK=3,///< blocked-=amount, available+=amount на wallet_from
BURN=4,///< изъятие amount с wallet_from->available, без wallet_to. Покрывает оба кейса: (a) штатное сжигание как бизнес-операция в OPERATION_REGISTRY; (b) зеркало ISSUE при `ledger2::revert` (различие — через operation_code: `o.adj.rev` для adjustment-mirror).
NONE=5,///< только бухпроводка без перемещения средств (wallet_from = empty, wallet_to = empty, debit ≠ 0, credit ≠ 0). Покрывает кейсы внутрибалансовых проводок типа Dr 04 / Cr 08 (приём РИД в НМА), когда кошелёк уже на нужном программном фонде.
BURN_BLOCKED=6,///< изъятие amount из wallet_from->blocked, без wallet_to. Используется при завершении возврата паевого (`o.wal.wthcpl`): средства предварительно заблокированы через BLOCK (REQUEST_WITHDRAW), на завершении сжигаются — выходят из системы (получателя на цепи нет). На L3 синхронно: from_uw->blocked -= amount.
};
/**
@@ -145,7 +154,7 @@ enum class WalletOp : uint8_t {
staticconstexpreosio::nameCK_MEMBER="w.wal.member"_n;///< ЦК — членская часть пайщика (USER_SHARED)
staticconstexpreosio::nameWITHDRAWALS_SINK="w.wal.wthdrw"_n;///< DEPRECATED 2026-05-21: после переключения o.wal.wthcpl с TRANSFER на BURN_BLOCKED кошелёк больше не получает новых средств. Оставлен в реестре для исторических L2-балансов (накопленные возвраты до перехода). Не использовать в новых операциях.
staticconstexpreosio::nameWITHDRAWALS_SINK="w.wal.wthdrw"_n;///< DEPRECATED 2026-05-21: исторический sink возвратов. Оставлен в реестре для исторических L2-балансов (накопленные возвраты до перехода). Не использовать в новых операциях.
staticconstexpreosio::nameWITHDRAW_PENDING="w.wal.wpend"_n;///< Резерв паевого под заявку на возврат (COOPERATIVE-пул). o.wal.wthreq переводит сюда с w.wal.share, o.wal.wthdec возвращает обратно, o.wal.wthcpl сжигает отсюда. Заменил механику blocked/BLOCK/UNBLOCK 2026-05-24.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.