From 8f58995fd0cb62896286dc32d0528df9d79a5a50 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 23 Oct 2026 16:46:41 +0300 Subject: [PATCH 1/4] fix: complete Tunduk interface corrections --- CHANGELOG.md | 5 + DESIGN.md | 2 +- backend/search/api.py | 2 +- backend/search/minjust_opensearch.py | 4 +- backend/search/review.html | 411 +++++++++++++++--- backend/test_search_api.py | 26 +- docs/design/tunduk-account-target.png | Bin 0 -> 142867 bytes docs/design/tunduk-review-target.png | Bin 0 -> 135903 bytes docs/design/tunduk-search-target.png | Bin 0 -> 161271 bytes docs/product/interface-correction-plan.md | 303 +++++++++++++ docs/product/visual-review/desktop-1024px.png | Bin 195766 -> 131014 bytes docs/product/visual-review/desktop-1280px.png | Bin 0 -> 203827 bytes .../visual-review/desktop-1280x800.png | Bin 189486 -> 186563 bytes docs/product/visual-review/desktop-1440px.png | Bin 204387 -> 206144 bytes docs/product/visual-review/mobile-390px.png | Bin 106395 -> 116146 bytes docs/product/visual-review/overlay-50.png | Bin 1120437 -> 465163 bytes .../visual-review/pixel-difference.png | Bin 717324 -> 318693 bytes .../region-document-difference.png | Bin 293128 -> 128865 bytes .../visual-review/region-document-overlay.png | Bin 453117 -> 185665 bytes .../region-filters-difference.png | Bin 93083 -> 44742 bytes .../visual-review/region-filters-overlay.png | Bin 136821 -> 63026 bytes .../region-footer-difference.png | Bin 35945 -> 15238 bytes .../visual-review/region-footer-overlay.png | Bin 63442 -> 21489 bytes .../region-header-difference.png | Bin 98014 -> 39301 bytes .../visual-review/region-header-overlay.png | Bin 166205 -> 57692 bytes .../region-results-difference.png | Bin 198130 -> 92315 bytes .../visual-review/region-results-overlay.png | Bin 300045 -> 138370 bytes .../visual-review/review-tour-desktop.png | Bin 91920 -> 119375 bytes .../visual-review/review-tour-mobile.png | Bin 59563 -> 59015 bytes tools/frontend-visual-review/check.cjs | 168 ++++++- 30 files changed, 840 insertions(+), 81 deletions(-) create mode 100755 docs/design/tunduk-account-target.png create mode 100755 docs/design/tunduk-review-target.png create mode 100755 docs/design/tunduk-search-target.png create mode 100755 docs/product/interface-correction-plan.md create mode 100644 docs/product/visual-review/desktop-1280px.png diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d169a..a3d83ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Не выпущено +- Frontend `0.2.4` / Backend `0.13.1`: исправлены состояния и адаптивный поиск, + чтение документа и лаборатории, доступные диалоги; управление пользователями + вынесено на отдельный защищённый экран. Документная разметка очищается от + активного HTML и небезопасных ссылок. Добавлены проверки маршрутов и reflow; + правовой текст и API управления пользователями не менялись. - Frontend `0.2.3`: добавлен публичный поиск по правовой базе с фильтрами, адаптивной выдачей, просмотром редакций и поиском по тексту документа; внутренняя лаборатория сохранена, её подсказки стали компактнее и привязаны diff --git a/DESIGN.md b/DESIGN.md index 54ac9e7..49d4edc 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -92,7 +92,7 @@ spacing: до реализации; утверждённые токены не заменяются цветами, взятыми пипеткой из растрового изображения. Изменения постоянных правил вносятся сюда явно. Этапы работ, исходные измерения и критерии визуальной приёмки находятся в -[плане точного воспроизведения поискового интерфейса](docs/product/frontend-pixel-perfect-plan.md). +[едином плане корректировки UI/UX](docs/product/interface-correction-plan.md). План не является второй дизайн-системой и не означает завершение реализации. **Key Characteristics:** diff --git a/backend/search/api.py b/backend/search/api.py index 46ee5a6..bea9111 100644 --- a/backend/search/api.py +++ b/backend/search/api.py @@ -395,7 +395,7 @@ def handler(api: Api): session = cookies.get("akyldash_session") token = session.value if session else None user = api.auth_store.user_for_token(token) - if method == "GET" and route in {"/", "/review"}: + if method == "GET" and route in {"/", "/review", "/users"}: body = api.review_page().encode() self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") diff --git a/backend/search/minjust_opensearch.py b/backend/search/minjust_opensearch.py index 10b7049..4bde78b 100644 --- a/backend/search/minjust_opensearch.py +++ b/backend/search/minjust_opensearch.py @@ -17,7 +17,7 @@ from typing import Iterator from search.catalog import authority_codes, legal_force_code, source_code -APP_VERSION = "0.13.0" +APP_VERSION = "0.13.1" LANGUAGES = {"ru", "ky"} DEFAULT_MAPPING = Path(__file__).with_name("minjust-fragments-index.json") @@ -465,7 +465,7 @@ def main() -> int: else: documents, fragments = export_bulk(arguments.input, arguments.output, arguments.index, arguments.limit) destination = arguments.output - print(f"documents={documents} fragments={fragments} destination={destination}\nAkyldash · Backend v{APP_VERSION} · Frontend v0.1.2") + print(f"documents={documents} fragments={fragments} destination={destination}\nAkyldash · Backend v{APP_VERSION} · Frontend v0.2.4") return 0 diff --git a/backend/search/review.html b/backend/search/review.html index ba7de2d..6b49b49 100644 --- a/backend/search/review.html +++ b/backend/search/review.html @@ -148,6 +148,130 @@ @media (max-width: 480px) { .review__search { grid-template-columns: 1fr; } .review__search .review__field:first-child, .review__search button { grid-column: auto; } .review__rating { grid-template-columns: repeat(2, minmax(0, 1fr)); } .search__query-row { grid-template-columns: 1fr 1fr; } .search__query-row > input { grid-column: 1 / -1; } .review__nav { align-items: start; flex-wrap: wrap; } .review__nav-links { order: 3; width: 100%; flex-wrap: wrap; } .review__nav-account { margin-inline-start: auto; } } @media (hover: hover) { button:hover { background: var(--surface-muted); border-color: var(--muted); } .review__button--primary:hover { background: var(--brand-dark); border-color: var(--brand-dark); } .review__result:hover { background: var(--subtle); } .review__result-title:hover, .search__result-title:hover { background: none; color: var(--brand); } .review__rating label:hover { border-color: var(--brand); } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; } } + /* Route-specific layout keeps reading and review controls in view without nested page shells. */ + body:has(#review:not([hidden])) { min-height: 100dvh; display: flex; flex-direction: column; } + body:has(#review:not([hidden])) > .review__header { width: 100%; } + body:has(#review:not([hidden])) > #review { width: min(100%, 1600px); flex: 1; } + body:has(#review:not([hidden])) > .search__footer { display: none; } + body:has(#review:not([hidden])) > .review__actions { width: min(100%, 1600px); margin: auto auto 0; } + body:has(#review:not([hidden])) > footer.review__header { width: 100%; margin-block-start: 1rem; } + .review__header-content, .review__actions-inner { max-width: 1600px; } + .review__header { padding-block-end: .5rem; } + .review__header-content h1 { margin-block: .6rem .25rem; } + .review__workspace { grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); } + .review__document { position: static; max-height: min(68dvh, 860px); overflow: auto; overscroll-behavior: contain; } + .review__document-body { max-width: 72ch; margin-inline: auto; } + .review__document-body table { display: block; max-width: 100%; overflow-x: auto; } + .review__document-body p:empty { display: none; } + .review__document-body a { color: var(--brand-dark); text-underline-offset: .18em; } + .search__layout[data-filters-hidden="true"] { grid-template-columns: minmax(320px, .8fr) minmax(0, 1.5fr); } + .search__layout[data-filters-hidden="true"] .search__filters { display: none; } + .search__layout.is-expanded { position: fixed; z-index: 15; inset: 0; display: block; overflow: auto; background: var(--surface); } + .search__layout.is-expanded .search__filters, .search__layout.is-expanded .search__results { display: none; } + .search__layout.is-expanded .search__document { min-height: 100%; padding: 24px clamp(16px, 6vw, 96px); } + .search__layout.is-expanded .search__document-body { max-width: 78ch; margin-inline: auto; } + .search__toolbar-actions { display: flex; align-items: center; gap: .5rem; } + .search__layout:not([data-has-editions="true"]) .search__document-toolbar { display: none; } + .search__layout:not([data-has-document="true"]) .search__find, + .search__layout:not([data-has-document="true"]) #document-expand, + .search__layout:not([data-has-document="true"]) #document-print, + .search__layout:not([data-has-document="true"]) .search__toc, + .search__layout:not([data-has-document="true"]) #document-source { display: none; } + .search__layout:not([data-has-document="true"]) .search__document-body { display: none; } + .search__mobile-filters { display: inline-flex; } + .search__account button { display: inline-flex; width: auto; min-height: 44px; gap: 8px; align-items: center; padding: 8px 12px; } + .search__account-label { display: inline; } + .search__document-body table, .review__document-body table { display: block; max-width: 100%; overflow-x: auto; } + .search__table-scroll { max-width: 100%; margin-block: 1rem; overflow-x: auto; overscroll-behavior-inline: contain; } + .search__table-scroll table { width: max-content; min-width: 100%; border-collapse: collapse; } + .search__filters .review__panel-heading { align-items: center; flex-wrap: wrap; } + .search__filters .review__panel-heading h2 { flex: 1 1 auto; } + .search__filters .review__panel-heading button { flex: 0 0 auto; } + .search__filter label { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; gap: 8px; min-height: 40px; } + .search__filter label span { min-width: 0; overflow-wrap: anywhere; } + .search__filter-count { color: var(--muted); font-size: .875rem; font-variant-numeric: tabular-nums; white-space: nowrap; } + .search__filter-more { margin-block-start: .4rem; } + .search__filter-more summary { min-height: 40px; display: flex; align-items: center; color: var(--brand-dark); cursor: pointer; } + .search__language-chip select { width: auto; min-width: 126px; max-width: none; } + .search__document-toolbar select { width: auto; min-width: 160px; max-width: min(280px, 100%); } + .search__document-toolbar .search__edition-label select { width: auto; min-width: 220px; max-width: min(360px, 100%); } + .search__source-link { max-width: none; white-space: normal; } + .review__rating { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .review__rating label { justify-items: start; text-align: start; padding-inline: .6rem; } + .review__comment-disclosure { margin-block: .75rem; } + .review__comment-disclosure summary { color: var(--brand-dark); cursor: pointer; } + .review__actions-inner { align-items: end; } + #save:disabled { cursor: not-allowed; } + dialog { width: min(640px, calc(100vw - 32px)); max-height: min(88dvh, 900px); overflow: auto; padding: 0; } + dialog > .review__page, dialog > section { max-width: none; padding: 24px; } + dialog h2 { margin: 0; padding-inline-end: 52px; } + .review__dialog-heading { position: sticky; z-index: 1; inset-block-start: -24px; display: flex; justify-content: space-between; align-items: start; gap: 1rem; margin: -24px -24px 16px; padding: 18px 24px; background: var(--surface); border-block-end: 1px solid var(--line); } + .review__dialog-heading h2 { margin: 0; font: 600 1.5rem/1.3 var(--serif); } + .review__dialog-heading button { flex: 0 0 44px; width: 44px; min-height: 44px; padding: 0; } + .review__dialog-heading button svg { width: 20px; height: 20px; } + body.is-users-route { min-height: 100dvh; display: flex; flex-direction: column; } + body.is-users-route > .review__header { display: block; width: 100%; padding-block-end: 0; } + body.is-users-route > .review__header > .review__header-content { display: none; } + body.is-users-route > footer.review__header { display: block; margin-block-start: auto; } + .review__users-page { width: min(1200px, 100%); flex: 1; margin-inline: auto; padding: 28px clamp(16px, 4vw, 48px); } + .review__users-page[hidden] { display: none !important; } + .review__users-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-block-end: 24px; } + .review__user-table-head, .review__user-row { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, .8fr) minmax(0, 1.2fr) minmax(0, 1.2fr) minmax(180px, 1.5fr); align-items: start; gap: 12px; } + .review__user-table-head { padding: 12px; color: var(--muted); font-size: .875rem; border-block-end: 1px solid var(--line); } + .review__user-list { list-style: none; margin: 0; padding: 0; } + .review__user-row { padding: 16px 12px; border-block-end: 1px solid var(--line); } + .review__user-row > * { min-width: 0; overflow-wrap: anywhere; } + .review__user-row details { grid-column: 1 / -1; } + .review__user-row details summary { min-height: 40px; display: flex; align-items: center; color: var(--brand-dark); cursor: pointer; } + .review__user-actions { display: flex; flex-wrap: wrap; gap: .5rem; } + .review__account-tabs { display: flex; gap: .5rem; margin-block: 1rem; border-block-end: 1px solid var(--line); } + .review__account-tabs [aria-selected="true"] { color: var(--brand-dark); border-color: var(--brand); background: var(--subtle); } + @media (max-width: 1100px) { + .search__layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); } + .search__layout:not([data-filters-hidden="true"]) .search__filters { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 24px; border-inline-end: 0; border-block-end: 1px solid var(--line); } + .search__layout:not([data-filters-hidden="true"]) .search__filters > .review__panel-heading, + .search__layout:not([data-filters-hidden="true"]) .search__filters > .search__dates, + .search__layout:not([data-filters-hidden="true"]) .search__filters > .search__date-label, + .search__layout:not([data-filters-hidden="true"]) .search__filters > #filter-fields { grid-column: 1 / -1; } + .search__filters .search__ornament { display: none; } + } + @media (max-width: 900px) { + body:has(.search:not([hidden])) { height: auto; min-height: 100dvh; overflow: visible; } + .review__workspace { grid-template-columns: minmax(0, 1fr); } + .review__document { max-height: 62dvh; } + .search__layout:not([data-filters-hidden="true"]) .search__filters { display: none; } + .search__layout:not([data-filters-hidden="true"]) .search__filters.is-open { display: block; } + } + @media (max-width: 600px) { + .review__users-heading { align-items: stretch; flex-direction: column; } + .review__user-table-head { display: none; } + .review__user-row { grid-template-columns: minmax(0, 1fr); gap: 8px; } + .review__user-row > [data-label]::before { content: attr(data-label) ": "; color: var(--muted); font-weight: 600; } + .review__user-row .review__user-actions { grid-column: 1; } + .review__nav { flex-wrap: wrap; } + .review__nav-links { flex-wrap: wrap; } + .review__nav-links a, .review__nav-links button { min-height: 44px; } + .search__topbar { grid-template-columns: minmax(0, 1fr) auto; } + .search__account { grid-column: auto; grid-row: 1; } + .search__account button { width: 48px; min-height: 48px; } + .search__account-label { display: none; } + .search__subnav { flex-wrap: wrap; } + .search__language-chip select { min-width: 126px; } + .search__layout { display: block; } + .search__filters { overflow: visible; } + .search__filter label { grid-template-columns: 24px minmax(0, 1fr) auto; } + .search__dates { grid-template-columns: minmax(0, 1fr); } + .search__document-toolbar { align-items: stretch; } + .search__document-toolbar > *, .search__document-toolbar label { max-width: 100%; } + .search__document-toolbar .search__edition-label select { min-width: min(220px, calc(100vw - 48px)); } + .search__find { grid-template-columns: minmax(0, 1fr) auto auto; } + .search__find input { grid-column: 1 / -1; } + .search__find label { grid-column: 1 / -1; } + .review__actions-inner { display: grid; grid-template-columns: minmax(0, 1fr); } + dialog { max-width: calc(100vw - 24px); max-height: calc(100dvh - 24px); } + dialog > .review__page, dialog > section { padding: 16px; } + .review__dialog-heading { inset-block-start: -16px; margin: -16px -16px 12px; padding: 12px 16px; } + } :root { --color-bg: #F7F2E8; --color-surface: #fff; --color-surface-muted: #FBF8F4; --color-text: #38262A; --color-text-secondary: #786469; --color-border: #DED3CE; --color-primary: #9D2633; --color-primary-hover: #84202B; --color-accent: #A37B32; --color-highlight: #FFF0C7; --color-focus: #245C78; --color-success: #246843; --search-header-height: 76px; --search-nav-height: 50px; } body:has(.search:not([hidden])) { width: 100%; height: 100dvh; min-height: 0; overflow: hidden; display: flex; flex-direction: column; } body:has(.search:not([hidden])) > .review__header { display: none; } @@ -207,6 +331,7 @@ .search__filter input[type="radio"]:checked::after { content: ''; position: absolute; width: 6px; height: 11px; inset: 3px 7px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg); } .search__filter--authority { display: grid; gap: 5px; margin-block: 0 20px; } .search__filter--authority label { display: grid; min-height: auto; gap: 5px; font-weight: 600; } + .search__filter--authority label { grid-template-columns: minmax(0, 1fr); } .search__filter--authority select { min-height: 42px; padding-block: 7px; font-weight: 400; } .search__date-label { display: block; margin-block: 0 7px; font-size: 16px; font-weight: 600; } .search__dates { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } @@ -230,6 +355,7 @@ .search__result-snippet { margin: 6px 0 0; color: var(--ink); font-size: 15px; line-height: 1.45; } .search__result-snippet mark, .search__document-body mark { color: inherit; background: var(--color-highlight); padding: 0 2px; } .search__document-body mark.is-current { outline: 2px solid var(--color-accent); outline-offset: 1px; } + .search__document-body a { color: var(--brand-dark); text-underline-offset: .18em; } .search__result em { font-style: normal; } .search__pagination { padding-block-start: 12px; } .search__pagination button { min-height: 40px; padding: 6px 12px; } @@ -336,14 +462,30 @@ .search, .search__layout, .search__document { display: block; width: auto; min-height: 0; overflow: visible; border: 0; padding: 0; } .search__document-body { max-width: none; } } + /* Route visibility must win over broad legacy :has() layout selectors. */ + body.is-users-route { width: 100%; height: auto; min-height: 100dvh; overflow: visible; display: flex; flex-direction: column; } + body.is-users-route > header.review__header { display: block; width: 100%; padding-block-end: 0; } + body.is-users-route > header.review__header > .review__header-content { display: none; } + body.is-users-route > footer.review__header { display: block; width: 100%; margin-block-start: auto; } + .search__layout { grid-template-columns: clamp(256px, 19.45vw, 280px) clamp(360px, 30vw, 408px) minmax(560px, 1fr); } + .search__account button { width: auto; min-width: 44px; min-height: 44px; padding: 8px 12px; } + @media (min-width: 901px) and (max-width: 1100px) { + .search__layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); } + .search__layout:not([data-filters-hidden="true"]) .search__filters { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 24px; } + .search__layout:not([data-filters-hidden="true"]) .search__filters > .review__panel-heading, + .search__layout:not([data-filters-hidden="true"]) .search__filters > .search__dates, + .search__layout:not([data-filters-hidden="true"]) .search__filters > .search__date-label, + .search__layout:not([data-filters-hidden="true"]) .search__filters > #filter-fields { grid-column: 1 / -1; } + } + @media (max-width: 600px) { .search__account button { width: 48px; padding: 8px; } } Перейти к результатам
-
Акылдаш
+
Акылдаш
-

Проверка качества поиска

Сопоставляйте запрос с текстом документов и сохраняйте оценку выдачи.

+

Проверка качества поиска

Сопоставляйте запрос с текстом документов и сохраняйте оценку выдачи.

@@ -354,7 +496,7 @@
- +
- -
+ +

Черновик появится после поиска.

Вход в Акылдаш

Введите учетные данные, выданные администратором.

Создайте новый пароль

Для продолжения замените временный пароль. Используйте не менее 12 символов.

-

Личный кабинет

Логин
Роль
Администратор
Дата создания

Изменить пароль

Новый пароль должен содержать не менее 12 символов.

-

Управление пользователями

Пароль — от 12 символов. Новый пользователь сменит его при первом входе. Передайте временный пароль отдельно.

    +

    Личный кабинет

    Логин
    Роль
    Администратор
    Дата создания
    +
    +

    Пользователи

    Права на действия определяются сервером.

    + +
    +
    ЛогинРольСостояниеПоследний входДействия
    +
      +
      +
      +

      Новый пользователь

      Не менее 12 символов. При первом входе пользователь должен изменить временный пароль.

      +

      Удалить пользователя

      Документ

      Источники документов

      Поиск выполняется по документам, подготовленным из правовых источников Кыргызской Республики. Для каждого результата доступная редакция и ссылка на официальный источник показываются в панели документа.

      Официальный источник: Централизованный банк данных правовой информации Министерства юстиции Кыргызской Республики.

      Короткое знакомство

      - +