boxframe 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boxframe-0.1.0/.github/workflows/publish.yml +49 -0
- boxframe-0.1.0/.github/workflows/static.yml +43 -0
- boxframe-0.1.0/.gitignore +16 -0
- boxframe-0.1.0/AGENTS.md +206 -0
- boxframe-0.1.0/Dockerfile +12 -0
- boxframe-0.1.0/PKG-INFO +370 -0
- boxframe-0.1.0/README.md +349 -0
- boxframe-0.1.0/boxframe/__init__.py +8 -0
- boxframe-0.1.0/boxframe/api/__init__.py +4 -0
- boxframe-0.1.0/boxframe/api/layouts.py +349 -0
- boxframe-0.1.0/boxframe/api/projects.py +143 -0
- boxframe-0.1.0/boxframe/api/test_cleanup.py +62 -0
- boxframe-0.1.0/boxframe/config.py +14 -0
- boxframe-0.1.0/boxframe/database.py +62 -0
- boxframe-0.1.0/boxframe/main.py +177 -0
- boxframe-0.1.0/boxframe/models/__init__.py +5 -0
- boxframe-0.1.0/boxframe/models/block.py +51 -0
- boxframe-0.1.0/boxframe/models/layout.py +29 -0
- boxframe-0.1.0/boxframe/models/project.py +18 -0
- boxframe-0.1.0/boxframe/services/__init__.py +11 -0
- boxframe-0.1.0/boxframe/services/layout_service.py +339 -0
- boxframe-0.1.0/boxframe/services/renderer.py +546 -0
- boxframe-0.1.0/boxframe/static/css/editor.css +553 -0
- boxframe-0.1.0/boxframe/static/css/style.css +211 -0
- boxframe-0.1.0/boxframe/static/editor/index.html +183 -0
- boxframe-0.1.0/boxframe/static/index.html +23 -0
- boxframe-0.1.0/boxframe/static/js/alpine.min.js +5 -0
- boxframe-0.1.0/boxframe/static/js/core/history.js +368 -0
- boxframe-0.1.0/boxframe/static/js/core/renderer.js +570 -0
- boxframe-0.1.0/boxframe/static/js/core/store.js +388 -0
- boxframe-0.1.0/boxframe/static/js/editor.js +1081 -0
- boxframe-0.1.0/boxframe/templates/pages/404.html +11 -0
- boxframe-0.1.0/boxframe/templates/pages/base.html +28 -0
- boxframe-0.1.0/boxframe/templates/pages/editor.html +190 -0
- boxframe-0.1.0/boxframe/templates/pages/project.html +76 -0
- boxframe-0.1.0/boxframe/templates/pages/projects.html +55 -0
- boxframe-0.1.0/index.html +22 -0
- boxframe-0.1.0/pyproject.toml +40 -0
- boxframe-0.1.0/requirements.txt +9 -0
- boxframe-0.1.0/tasks/0000-template.md +25 -0
- boxframe-0.1.0/tasks/0001-0010/0001-fix-startup-and-add-testing.md +78 -0
- boxframe-0.1.0/tasks/0001-0010/0002-e2e-tests-cleanup.md +52 -0
- boxframe-0.1.0/tasks/0001-0010/0003-fix-new-layout-button.md +94 -0
- boxframe-0.1.0/tasks/0001-0010/0004-e2e-test-data-cleanup.md +50 -0
- boxframe-0.1.0/tasks/0001-0010/0005-add-server-logging.md +41 -0
- boxframe-0.1.0/tasks/0001-0010/0006-drag-drop-and-project-page-fix.md +84 -0
- boxframe-0.1.0/tasks/0001-0010/0007-add-block-resize-by-corner.md +85 -0
- boxframe-0.1.0/tasks/0001-0010/0008-add-block-z-order.md +115 -0
- boxframe-0.1.0/tasks/0001-0010/0009-auto-order-assignment.md +98 -0
- boxframe-0.1.0/tasks/0001-0010/0010-boxframe-layout.md +139 -0
- boxframe-0.1.0/tasks/0011-0020/0011-delete-layout-from-project.md +85 -0
- boxframe-0.1.0/tasks/0011-0020/0012-edit-block-content-inline.md +112 -0
- boxframe-0.1.0/tasks/0011-0020/0013-word-wrap-in-pseudo-graphics.md +81 -0
- boxframe-0.1.0/tasks/0011-0020/0014-line-blocks-and-border-styles.md +133 -0
- boxframe-0.1.0/tasks/0011-0020/0015-show-frame-for-none-border.md +87 -0
- boxframe-0.1.0/tasks/0011-0020/0016-single-click-block-selection.md +135 -0
- boxframe-0.1.0/tasks/0011-0020/0017-block-properties-panel.md +118 -0
- boxframe-0.1.0/tasks/0011-0020/0018-static-editor.md +143 -0
- boxframe-0.1.0/tasks/0011-0020/0019-unrestricted-block-placement.md +134 -0
- boxframe-0.1.0/tasks/0011-0020/0020-canvas-art-overlay-alignment.md +127 -0
- boxframe-0.1.0/tasks/0021-0030/0021-word-wrap-preserve-spaces.md +111 -0
- boxframe-0.1.0/tasks/0021-0030/0022-button.md +146 -0
- boxframe-0.1.0/tasks/0021-0030/0023-editor-canvas-ux.md +158 -0
- boxframe-0.1.0/tasks/0021-0030/0024-optional-layout-dimensions.md +183 -0
- boxframe-0.1.0/tasks/0021-0030/0025-static-editor-persistence.md +155 -0
- boxframe-0.1.0/tasks/0021-0030/0026-web-editor-clear-layout.md +155 -0
- boxframe-0.1.0/tasks/0021-0030/0027-editor-undo-redo.md +224 -0
- boxframe-0.1.0/tasks/0021-0030/0028-persistent-undo-buffer.md +168 -0
- boxframe-0.1.0/tasks/0021-0030/0029-pypi-publishing.md +170 -0
- boxframe-0.1.0/tasks/README.md +53 -0
- boxframe-0.1.0/tasks/backlog/todo-0001-component-variants-completion.md +130 -0
- boxframe-0.1.0/tests/__init__.py +0 -0
- boxframe-0.1.0/tests/api/__init__.py +0 -0
- boxframe-0.1.0/tests/api/test_layouts.py +748 -0
- boxframe-0.1.0/tests/api/test_projects.py +90 -0
- boxframe-0.1.0/tests/conftest.py +81 -0
- boxframe-0.1.0/tests/e2e/__init__.py +0 -0
- boxframe-0.1.0/tests/e2e/conftest.py +44 -0
- boxframe-0.1.0/tests/e2e/test_drag_drop.py +221 -0
- boxframe-0.1.0/tests/e2e/test_editor.py +543 -0
- boxframe-0.1.0/tests/e2e/test_projects.py +147 -0
- boxframe-0.1.0/tests/js/test_history.js +482 -0
- boxframe-0.1.0/tests/js/test_renderer.js +732 -0
- boxframe-0.1.0/tests/js/test_store.js +156 -0
- boxframe-0.1.0/tests/test_js_renderer_parity.py +170 -0
- boxframe-0.1.0/tests/test_renderer.py +817 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
repository:
|
|
9
|
+
description: "Target repository"
|
|
10
|
+
type: choice
|
|
11
|
+
default: testpypi
|
|
12
|
+
options:
|
|
13
|
+
- testpypi
|
|
14
|
+
- pypi
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
id-token: write
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.12"
|
|
28
|
+
- uses: actions/setup-node@v4
|
|
29
|
+
with:
|
|
30
|
+
node-version: "24"
|
|
31
|
+
- run: pip install -e ".[dev]"
|
|
32
|
+
- run: pytest tests/ --ignore=tests/e2e
|
|
33
|
+
# unquoted glob: bash expands it; node < 21 does not glob on its own
|
|
34
|
+
- run: node --test tests/js/*.js
|
|
35
|
+
|
|
36
|
+
publish:
|
|
37
|
+
needs: test
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
- uses: actions/setup-python@v5
|
|
42
|
+
with:
|
|
43
|
+
python-version: "3.12"
|
|
44
|
+
- run: pip install build
|
|
45
|
+
- run: python -m build
|
|
46
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
47
|
+
with:
|
|
48
|
+
# tag push → pypi; manual run → input (default: testpypi)
|
|
49
|
+
repository: ${{ github.event.inputs.repository || 'pypi' }}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Simple workflow for deploying static content to GitHub Pages
|
|
2
|
+
name: Deploy static content to Pages
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
# Runs on pushes targeting the default branch
|
|
6
|
+
push:
|
|
7
|
+
branches: ["develop"]
|
|
8
|
+
|
|
9
|
+
# Allows you to run this workflow manually from the Actions tab
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
pages: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
|
19
|
+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
|
20
|
+
concurrency:
|
|
21
|
+
group: "pages"
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
# Single deploy job since we're just deploying
|
|
26
|
+
deploy:
|
|
27
|
+
environment:
|
|
28
|
+
name: github-pages
|
|
29
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
- name: Setup Pages
|
|
35
|
+
uses: actions/configure-pages@v5
|
|
36
|
+
- name: Upload artifact
|
|
37
|
+
uses: actions/upload-pages-artifact@v3
|
|
38
|
+
with:
|
|
39
|
+
# Upload entire repository
|
|
40
|
+
path: 'boxframe/static'
|
|
41
|
+
- name: Deploy to GitHub Pages
|
|
42
|
+
id: deployment
|
|
43
|
+
uses: actions/deploy-pages@v5
|
boxframe-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# AGENTS.md — boxframe
|
|
2
|
+
|
|
3
|
+
## О проекте
|
|
4
|
+
|
|
5
|
+
**boxframe** — редактор UI-макетов в псевдографике (ASCII/Unicode box-drawing). Макеты машиночитаемы, самодокументируемы и оптимизированы для потребления ИИ-агентами.
|
|
6
|
+
|
|
7
|
+
**Стек:** Python 3.11+, FastAPI, SQLAlchemy (aiosqlite), Jinja2, HTMX 2, Alpine.js 3.
|
|
8
|
+
|
|
9
|
+
## Структура
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
boxframe/
|
|
13
|
+
├── boxframe/
|
|
14
|
+
│ ├── api/ # FastAPI роутеры
|
|
15
|
+
│ │ ├── projects.py # CRUD проектов
|
|
16
|
+
│ │ └── layouts.py # CRUD layout/blocks + render + export
|
|
17
|
+
│ ├── models/ # SQLAlchemy ORM
|
|
18
|
+
│ │ ├── project.py # Project
|
|
19
|
+
│ │ ├── layout.py # Layout (width×height)
|
|
20
|
+
│ │ └── block.py # Block (x,y,w,h, type, content, border)
|
|
21
|
+
│ ├── services/ # Бизнес-логика
|
|
22
|
+
│ │ ├── layout_service.py # CRUD + render + export
|
|
23
|
+
│ │ └── renderer.py # Псевдографика (Unicode box-drawing)
|
|
24
|
+
│ ├── static/ # Статика
|
|
25
|
+
│ │ ├── css/style.css # Dark theme
|
|
26
|
+
│ │ ├── css/editor.css # Стили редактора (общие для web и static)
|
|
27
|
+
│ │ ├── editor/index.html # Статический редактор (без бэкенда)
|
|
28
|
+
│ │ └── js/
|
|
29
|
+
│ │ ├── alpine.min.js # Alpine.js (вендор, без CDN)
|
|
30
|
+
│ │ ├── core/renderer.js# JS-порт рендерера (static-режим)
|
|
31
|
+
│ │ ├── core/store.js # FetchStore / LocalStorageStore / MemoryStore
|
|
32
|
+
│ │ ├── core/history.js # withHistory — undo/redo (snapshot-стеки)
|
|
33
|
+
│ │ └── editor.js # Alpine.js editor app (общий)
|
|
34
|
+
│ ├── templates/pages/ # Jinja2 шаблоны
|
|
35
|
+
│ │ ├── base.html
|
|
36
|
+
│ │ ├── projects.html
|
|
37
|
+
│ │ ├── project.html
|
|
38
|
+
│ │ └── editor.html
|
|
39
|
+
│ ├── config.py # Pydantic Settings
|
|
40
|
+
│ ├── database.py # Async engine + session
|
|
41
|
+
│ └── main.py # FastAPI app + page routes
|
|
42
|
+
├── tests/
|
|
43
|
+
│ ├── api/ # API unit-тесты (httpx + TestClient)
|
|
44
|
+
│ │ ├── test_projects.py # CRUD проектов
|
|
45
|
+
│ │ └── test_layouts.py # CRUD layout/blocks + render + export
|
|
46
|
+
│ ├── e2e/ # End-to-end тесты (Playwright)
|
|
47
|
+
│ │ ├── conftest.py # fixture reset_page_state
|
|
48
|
+
│ │ ├── test_editor.py # Загрузка страниц editor/project/index + static
|
|
49
|
+
│ │ └── test_projects.py # New Project / New Layout кнопки + форма
|
|
50
|
+
│ ├── js/test_renderer.js # Тесты JS-рендерера (node:test)
|
|
51
|
+
│ ├── js/test_store.js # Тесты JS-хранилищ (node:test)
|
|
52
|
+
│ ├── js/test_history.js # Тесты undo/redo-истории (node:test)
|
|
53
|
+
│ ├── conftest.py # API-файстуры (async engine + TestClient)
|
|
54
|
+
│ ├── test_js_renderer_parity.py # Parity Python↔JS рендереры
|
|
55
|
+
│ └── test_renderer.py # Тесты рендерера (ASCII + HTML-оверлей)
|
|
56
|
+
├── Dockerfile
|
|
57
|
+
├── pyproject.toml
|
|
58
|
+
├── requirements.txt
|
|
59
|
+
└── README.md
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Ключевые концепции
|
|
63
|
+
|
|
64
|
+
### Блоки
|
|
65
|
+
|
|
66
|
+
Типы: `box`, `header`, `footer`, `sidebar`, `content`, `button`, `input`, `textarea`, `image`, `divider`, `hline`, `vline`, `text`, `grid`
|
|
67
|
+
|
|
68
|
+
Стили границ: `solid` (─│), `dashed` (┄┆), `dotted` (┈┊), `double` (═║), `none` — применяются и к рамкам блоков, и к линиям (`hline`/`vline` рисуются символом выбранного стиля)
|
|
69
|
+
|
|
70
|
+
`button` — особая форма в зависимости от высоты (спецификация в `tasks/0021-0030/0022-button.md`):
|
|
71
|
+
- `h=1`: `[label ]`
|
|
72
|
+
- `h=2`: `│label │` + `└──────┘` (без верхней рамки)
|
|
73
|
+
- `h>=3`: полная рамка, label по центру по вертикали (для чётной h — верхняя средняя строка)
|
|
74
|
+
- label — одна строка: обрезается по внутренней ширине, перенос слов не применяется; `border_style=none` — только label
|
|
75
|
+
|
|
76
|
+
Дерево: `Block.parent_id → Block.children`, хранится в SQLite.
|
|
77
|
+
|
|
78
|
+
### Рендерер
|
|
79
|
+
|
|
80
|
+
`PseudoGraphicRenderer` — преобразует дерево блоков в ASCII-арт на сетке.
|
|
81
|
+
- Класс `RenderBlock` — плоское представление для рендера
|
|
82
|
+
- `render_simple()` — convenience-метод из raw dicts
|
|
83
|
+
- `render()` — рисует на 2D-сетке, обрабатывает вложенность
|
|
84
|
+
|
|
85
|
+
### Статический режим
|
|
86
|
+
|
|
87
|
+
Редактор работает без бэкенда: `static/editor/index.html` открывается
|
|
88
|
+
напрямую в браузере (даже через `file://`), макет хранится в localStorage.
|
|
89
|
+
|
|
90
|
+
- `core/store.js` — единый интерфейс хранилища: `FetchStore` (API, web-режим),
|
|
91
|
+
`LocalStorageStore` (static-режим, persist в localStorage, key
|
|
92
|
+
`boxframe.static.layout`, `options.storage` — для тестов) и `MemoryStore`
|
|
93
|
+
(in-memory, fallback); `editor.js` не знает о fetch. `clear()` (очистка
|
|
94
|
+
всего макета) есть только у memory/local-сторов
|
|
95
|
+
- `core/renderer.js` — JS-порт `services/renderer.py`: ASCII + HTML-оверлей;
|
|
96
|
+
вывод должен совпадать с Python побайтово (parity-тесты)
|
|
97
|
+
- Режим определяется по `data-layout-id` в DOM: есть → `FetchStore`,
|
|
98
|
+
нет → `LocalStorageStore`
|
|
99
|
+
- Блоки в JS — плоский список с `parent_id` (x/y детей — относительные,
|
|
100
|
+
как в БД); константы `BLOCK_TYPES`/`BORDER_STYLES` дублируются в
|
|
101
|
+
`core/renderer.js` (держать в синхроне с `models/block.py`)
|
|
102
|
+
|
|
103
|
+
### API
|
|
104
|
+
|
|
105
|
+
| Method | Path | Description |
|
|
106
|
+
|--------|------|-------------|
|
|
107
|
+
| GET | `/api/projects/` | Список проектов |
|
|
108
|
+
| POST | `/api/projects/` | Создать проект |
|
|
109
|
+
| GET | `/api/projects/{id}/info` | Метаданные + доступные типы |
|
|
110
|
+
| DELETE | `/api/projects/{id}` | Удалить проект |
|
|
111
|
+
| POST | `/api/projects/{id}/layouts` | Создать layout |
|
|
112
|
+
| GET | `/api/layouts/{id}` | Layout + блоки |
|
|
113
|
+
| POST | `/api/layouts/{id}/blocks` | Добавить блок |
|
|
114
|
+
| PUT | `/api/layouts/{id}/blocks/{bid}` | Обновить блок |
|
|
115
|
+
| DELETE | `/api/layouts/{id}/blocks/{bid}` | Удалить блок |
|
|
116
|
+
| DELETE | `/api/layouts/{id}/blocks` | Удалить все блоки (layout остаётся) |
|
|
117
|
+
| PUT | `/api/layouts/{id}/blocks` | Полная замена набора блоков (undo/redo; id из payload сохраняются) |
|
|
118
|
+
| GET | `/api/layouts/{id}/render` | ASCII + HTML preview |
|
|
119
|
+
| GET | `/api/layouts/{id}/export` | JSON / Markdown / ASCII |
|
|
120
|
+
|
|
121
|
+
### Фронтенд
|
|
122
|
+
|
|
123
|
+
- **HTMX** — серверные рендеры страниц, без SPA
|
|
124
|
+
- **Alpine.js** — реактивность редактора (`x-data="editorApp()"`), подключается локально (`static/js/alpine.min.js`)
|
|
125
|
+
- **Редактор** — сайдбар (палитра + список элементов) + canvas-превью; общий для web- и static-режимов
|
|
126
|
+
- **Undo/Redo** — `core/history.js`: `withHistory(store)` оборачивает store,
|
|
127
|
+
хранит snapshot-стеки (без лимита) и восстанавливает состояние через
|
|
128
|
+
`replaceState` (local) / batch `PUT /blocks` (web). Снапшот берётся из
|
|
129
|
+
кэша состояния (seed от `load()`, синк от результатов мутаций) — без
|
|
130
|
+
доп. запросов. Coalescing: consecutive-обновления одного блока в окне
|
|
131
|
+
500 мс = одна запись. Хоткеи Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y (не
|
|
132
|
+
перехватываются в текстовых полях)
|
|
133
|
+
- Экспорт: скачивание файла (JSON/MD/ASCII)
|
|
134
|
+
|
|
135
|
+
## Соглашения
|
|
136
|
+
|
|
137
|
+
- **Python:** 100 символов строка, type hints, dataclasses для DTO
|
|
138
|
+
- **Именование:** snake_case для кода, kebab-case для URL
|
|
139
|
+
- **Тесты:** `tests/test_<module>.py`, pytest
|
|
140
|
+
- **БД:** асинхронная SQLAlchemy 2.0+, `async_session` через Depends
|
|
141
|
+
- **Безопасность:** CSRF не требуется (не авторизован), валидация через Pydantic
|
|
142
|
+
|
|
143
|
+
## Типичные задачи
|
|
144
|
+
|
|
145
|
+
### Добавить новый тип блока
|
|
146
|
+
|
|
147
|
+
1. Добавить в `BLOCK_TYPES` в `models/block.py` **и** в `static/js/core/renderer.js`
|
|
148
|
+
2. Добавить обработку в `renderer.py` и `core/renderer.js` (если нужна особая отрисовка)
|
|
149
|
+
3. Добавить кнопку в палитру `editor.html`
|
|
150
|
+
4. Добавить тесты в `tests/test_renderer.py` **и** `tests/js/test_renderer.js`
|
|
151
|
+
|
|
152
|
+
### Изменить API
|
|
153
|
+
|
|
154
|
+
1. Добавить роут в `api/layouts.py` или `api/projects.py`
|
|
155
|
+
2. Обновить `LayoutService` при необходимости
|
|
156
|
+
3. Добавить Pydantic-схему (create/update/out)
|
|
157
|
+
4. Протестировать через `httpx` в pytest
|
|
158
|
+
|
|
159
|
+
### Изменить рендерер
|
|
160
|
+
|
|
161
|
+
1. Править `services/renderer.py` **и** `static/js/core/renderer.js`
|
|
162
|
+
(порт должен давать идентичный вывод)
|
|
163
|
+
2. Убедиться, что `render_simple()` и `render()` согласованы
|
|
164
|
+
3. Добавить/обновить тесты в `tests/test_renderer.py` **и** `tests/js/test_renderer.js`
|
|
165
|
+
4. Прогнать parity-тесты: `pytest tests/test_js_renderer_parity.py`
|
|
166
|
+
5. Проверить, что `LayoutService.render_layout()` использует новый рендер
|
|
167
|
+
|
|
168
|
+
### Добавить страницу
|
|
169
|
+
|
|
170
|
+
1. Создать шаблон в `templates/pages/`
|
|
171
|
+
2. Добавить route в `main.py`
|
|
172
|
+
3. Добавить ссылки в существующие шаблоны
|
|
173
|
+
|
|
174
|
+
## Запуск
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pip install -r requirements.txt
|
|
178
|
+
uvicorn boxframe.main:app --reload
|
|
179
|
+
# http://localhost:8000
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Тесты:
|
|
183
|
+
```bash
|
|
184
|
+
pytest tests/ -v # unit + API
|
|
185
|
+
node --test "tests/js/*.js" # JS-рендерер (нужен Node.js)
|
|
186
|
+
pytest tests/e2e/ -v # e2e: нужен запущенный сервер на :8000
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Внимание: e2e и API-тесты запускать **раздельными командами** (playwright
|
|
190
|
+
sync API конфликтует с pytest-asyncio в одном процессе).
|
|
191
|
+
|
|
192
|
+
Docker:
|
|
193
|
+
```bash
|
|
194
|
+
docker build -t boxframe .
|
|
195
|
+
docker run -p 8000:8000 boxframe
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Чего НЕ делать
|
|
199
|
+
|
|
200
|
+
- Не менять формат псевдографики — это ядро продукта (AI-readable format)
|
|
201
|
+
- Не расхожить Python- и JS-рендереры — формат один; править оба + оба
|
|
202
|
+
набора тестов + parity
|
|
203
|
+
- Не добавлять авторизацию в MVP
|
|
204
|
+
- Не использовать сборщики (Webpack/Vite) — HTMX рендерит на сервере,
|
|
205
|
+
JS-файлы — обычные скрипты (должны работать из `file://`)
|
|
206
|
+
- Не менять SQLite на другую БЗ без веской причины (MVP-файловая БД — фича)
|