Writes the ТЗ на экран, a file per S-n, from Figma frames, OpenAPI and SRS. Use once mockups exist, for «ТЗ на экран входа», «ТЗ на форму». Not drawing (ui-design) or code (frontend).
Resources
2Install
npx skillscat add idontlikedrinkpivo/web-dev-pipeline-codex-plugin/screen-spec Install via the SkillsCat registry.
Codex runtime. Before using this skill, read
../executor-catalog/references/codex-runtime.md. It defines tool mappings, named-agent dispatch and stage-chat orchestration; use it for inherited Claude Code terms below.
Screen Spec
The frames show what a screen looks like; the API says what an operation
takes and returns; the SRS says who may do what. None of them says which
element calls which operation in which condition, which response field it
shows, and what the actor sees when that call answers 409. A front-end
developer left with the three decides those links alone, and each screen
decides them differently. The screen spec is that missing layer: one file
per screen, written after the mockups, read side by side with the frame.
Because it is written after the design, it may point at the frames — bynodeId and component variant — and quote their texts. It still never
describes the look in words: the frame is the look, and a sentence about a
green outline goes stale the day the designer changes it.
Write every file in Russian. Do not translate ids (S-, M1, UC-, A-,BR-, FR-), operationIds, field paths, or nodeIds. Template headings
stay as references/template.md writes them.
Not this skill's job
| Not here | Owner |
|---|---|
Drawing or changing frames, naming screens S-n, the «Тексты» wording |
ui-design — a frame that is wrong is a Разрыв with owner design |
| Adding or changing an operation or a response | openapi-spec-generator — a Разрыв with owner API |
| Deciding who may do what, or a business rule | the SRS (srs-writer) — a Разрыв with owner SRS |
| Screen code | frontend, through plan → work |
| Test cases | ui-test-cases (write), the stage after this one |
| Global interface behaviour (hover, focus ring, progress on a running request, Escape closes a dialog) | ux-patterns — it applies to every screen and is never restated here |
No human-facing surface means no frames register and no screens: the whole
design branch is skipped per pipeline → Skips. Do not invent a console.
Handed to another person? In the session of a person who took
this stage (pipeline → references/team.md): the version comes from the
branch iteration/<version>, inputs arrive by git pull --rebase, only this
stage's documents are written — each committed the moment it is finished
and pushed by the plugin once final, at the stage's close (pipeline →
Commit each document) — and the stage closes as usual, its stamp included.
After the last stage this person took, the «готовы и запушены»
line replaces the offer of a stage someone else has.
Inputs
| Input | Required | Use |
|---|---|---|
Frames register documentation/ui/frames-register.md |
always | header (fileKey, Тип продукта, Ширины, Доступность, «Слои по действиям»), sources: (the SRS pin); table «Экраны» — S-n, UC ids, actors, screen / state / nested-step nodeIds, status; table «Общие состояния» — the app-wide state frames (Общее · …). Missing → stop and name ui-design |
| The Figma file the register names, through the Figma MCP | always | layers named by action, variants, the S-n · Аннотация frame (Фокус line, numbered «Элементы», «Тексты»); in a designer's file with Слои по действиям: нет, the text layers of the screen, state and step frames themselves. MCP unavailable → stop: never invent a nodeId or a text. Tell the user to connect and authorise the Figma MCP once with /mcp |
documentation/api/openapi.yaml |
when the product has HTTP | every operationId, field path and declared status. Frames done but no API yet → stop and name openapi-spec-generator (pipeline: the screen specs wait for the contract) |
The SRS documentation/requirements/srs/srs.md |
always | actors (A-n) and what each role may do, UC ids, rule ids (BR-, FR-) to cite |
ux-patterns |
always | the completeness list of states, and what is global (not restated) |
The DB schema is not an input: a screen sees the API, never a column. The
architecture is not an input either. ui-wishes.md is never grounds for
anything here: a frame that departs from a wish is not a Разрыв.
Artifact
One file per screen: documentation/ui/screen-specs/S-<n>-<screen-slug>.md,
in the folder beside the frames register (S-1-schedule.md,S-4-booking-form.md). With several UI products each has its owndocumentation/ui/<product>/ holding its register and screen-specs/; one
product keeps everything straight in documentation/ui/.<screen-slug> is the screen name in kebab-case Latin, chosen once; the
filename never changes afterwards, because plans, tests and code cite it.
Resolve the repo root with git rev-parse --show-toplevel (cwd if not a git
repo).
Read references/template.md before writing or checking a file: it holds
the exact template and a filled example (S-1 «Расписание» of a room-booking
product).
Each file is a versioned document under doc-versioning: a new file is born
at the open iteration's service version (the documentation/plans/<version>/
folder; on greenfield the first iteration's number, usually 0.1.0) with one «первый выпуск» row in## Журнал изменений (the SRS's Russian columns). After that, writers edit
the body in place and never touch version, updated, or the journal; the
stamping commit (doc-versioning) writes the version and the rows. doc-review sets reviewed:
when its gate closes. Downstream of a screen spec: ui-test-cases and the
plan's screen units.
Questions the user defers during doc-review go to open-questions.md
beside the frames register, one file for all screens of the product, each
row starting with its S-n. Mismatches the writer finds are not questions:
they are Разрывы rows inside the screen's own file.
What each file holds
The sections, in order (template in references/template.md):
- Экран — actors with their conditions, UC ids, where the actor comes
from, whichS-nandM<n>it leads to, the narrow-width frame (the
register's375 → nodeId), and the Фокус table built from the
annotation's Фокус line: where focus lands at entry, after a nested
step closes, and after a failed submit, named by element number. - Загрузка и вызовы — an ordered list «условие →
operationId
(параметры)»: when the screen calls what, in the order the front makes
the calls. When a call's input comes from an earlier call's response, its
item names that earlier item and the field it takes: «2. После ответа 1 →getRoom(room_idиз ответа 1)». A reload after a mutation
is not such a dependency. The list is Frontend ↔ API only: no services,
databases or internal hops behind the API. A screen spec has no diagram;
the numbered items carry the order. - Элементы — one row per row of the annotation's «Элементы» table
(№ | слой | поток).№is that number: one numbering across the screen
and its nested steps, append-only, so a removed element leaves a gap and
this skill never renumbers. «Элемент» is the layer's name without its
prefix (действие: отмена брони→ «отмена брони»). «Поведение по
условиям» gives, per condition (role, a flag or value from the API, a
derived value), what the element shows and the outcome. «Метод и поля» names theoperationIdand the
response or request field paths (rooms[].bookings[].can_cancel;cancelBooking←bookingId=rooms[].bookings[].booking_id), orтекст кадраfor a static text. «Кадр» is thenodeIdand the component
variant (16:760 · Слот=Своя). A nested stepM<n>(M1, as the
register numbers it) is its own subsection under Элементы, with the rows
of its elements. - Поля ввода — only when the screen has inputs: field, required, the
rule (operationId→ property and its constraint, or an SRS id), when it
is checked (при уходе с поля,при отправке,ответ <код>), and the
error text verbatim from «Тексты». - Состояния экрана — every state the register's «Состояния» cell lists,
under the register's name (Loading,Empty,Error,Forbidden,Занято (UC-4 Exc-1)), and every other state from theux-patterns
completeness list that can happen on this screen: when it happens (the
call and the response that lead there), what is visible and what the
actor can do, its frame. App-wide states are one row pointing to the
register's «Общие состояния». A state that cannot happen is one line
saying why («Forbidden — не бывает: экран открыт обеим ролям, SRS §2»). - Ошибки ответов — every non-success status every called operation
declares, with what happens and its frame. Statuses the app handles the
same way everywhere (typically401,429,5xx, no network) are rows
markedобщий обработчик, each pointing to itsОбщее · …frame from the
register's «Общие состояния»; their text is identical in every screen
file of the product (Orchestrator step 4). - Производные значения — every value the front computes, defined once:
name, how it is computed from which fields, the SRS rule it implements; orиз API: <field>when the server already gives it. Element rows refer to
the name and never repeat the formula. - Разрывы — what the sync check found: what does not match, where
(element number,nodeId,operationId+ status, SRS id), the owner
(design|API|SRS), and what the spec says meanwhile.
The outcome of an action is a closed set
Every branch of an interactive element ends in exactly one of these:
| Outcome | When |
|---|---|
операция <operationId> |
the action calls HTTP; the id is copied from documentation/api/openapi.yaml. Its success is described in the element row, its errors in «Ошибки ответов» |
шаг M<n> |
opens a nested step of this screen (the layer шаг: M<n>; its frame is in the register's «Вложенные шаги») |
переход на S-n |
navigates, with the parameters it carries (room_id, starts_at) |
| закрытие | leaves the nested step or the screen |
| значение формы | the value waits and leaves with another element's operation |
| нет действия | the element only shows data in this condition |
An action that needs an operation the API lacks is written asоперация — нет в OpenAPI plus a Разрыв with owner API. Do not invent anoperationId.
What a file never contains
- A description of the look. No colours, sizes, borders, icons or
positions in words. «Своя бронь: зелёная заливка, значок человека» becomes
«вариантСлот=Своя(16:781)». The frame and the variant name carry it. - Wording that is not on the frame. Every text in «» is copied verbatim
from the frame: from the «Тексты» table on the screen'sS-n · Аннотация
frame, or — in a designer's file the register marks Слои по действиям:
нет, which has no annotation and no «Тексты» — from the text layer
itself, markedтекст слояwith itsnodeId(«Отменить бронь» (текст
слоя16:771)). Without that second source every text of such a file
would be a Разрыв andfrontendwould stop on every screen. A meaning
with no text on the frames is a Разрыв (design) withтекст — нет в «Тексты»(orтекст — нет на кадреin a file without annotation), never
a sentence the writer composes. A text layer belongs to the flow of the
frame it sits on — the register's state name carries the flow id
(Занято (UC-4 Exc-1)). The table is keyed by place and
SRS flow, not by HTTP status (S-1 · Empty,M1 · заголовок,№4 отмена брони · UC-3 Exc-1,№2 длительность · ошибка): to put a
text on a status row, follow the flow to the SRS named error and to the
status whose error code the operation declares for it (UC-3 Exc-1→BOOKING_STARTED→cancelBooking409). A flow that maps to no
declared status, or a status no flow explains, is a Разрыв. - Backend internals. No services behind the API, no SQL, no «бэкенд
сверяет бронь с календарём переговорной». The API is the boundary the front sees. - An implementation choice. No library or component name («Sonner»,
«MUI Dialog») and no timing the frame does not state: the stack andux-patternsdecide those; the spec names the outcome («сообщение об
успехе», UX-27). - Global behaviour. No «курсор меняется на pointer», no hover, no focus
ring, no «кнопка показывает загрузку» —ux-patternsholds those for every
screen. Only a screen-specific departure is written, with its
«Исключение UX-» from the frame's annotation. - A restated business rule. «Начавшуюся бронь отменить нельзя» is
the SRS's; the spec citesBR-4and, when the API computes it, the field
(can_cancel). The front never recomputes a rule the API already
returns; when the API does not return it and computing it on the front
would duplicate a business rule, that is a Разрыв (API). - A column name. A field path is from the API response, not the table.
The sync check
The writer runs it while writing, not afterwards; each miss is a Разрывы row.
- Every UC action of the screen's UC ids, as the SRS states it, has an element
row (or a nested-step row). An action with no element →design. - Every interactive element on the frame (layers
действие: …,ввод: …,шаг: M<n>) maps to an outcome from the closed set. An element with no action or operation
→designorSRS, whichever has to decide. - Every status an operation declares has a row in «Ошибки ответов» with an
outcome; a declared status with no frame or no text →design; a status
the screen must handle that the API does not declare →API. - Every data field the frame shows exists in an API response (or is a
derived value built from one). Missing →API, ordesignwhen the frame
shows data the SRS never asked for. - Every state in the register's «Состояния» has a frame, and every state the
screen can reach has a row. Missing frame →design. Which frame applies
at which width comes from the register's «Ширины»; a width between two
drawn frames with no stated switch point →design. - Every placeholder in a quoted text (
{interval},{N}) has a source: a
response or error-body field, a value the screen already holds, or a
derived value. A text that needs data no response returns →API. - Every input limit a frame shows (one date only, hours, length, a
required mark) matches the request schema and the SRS. The frame stricter
than both →designorSRS; the API looser than the SRS →API. Also
check the cases the SRS forbids and the frame does not show (a time in
the past, a range that ends before it starts). - Every identifier written —
operationId, field path, errorcode,
status — is copied fromdocumentation/api/openapi.yaml, case and
spelling included; grep each before writing it. A status or code the API
does not declare is not written as declared; it is a Разрыв (API).
Before a Разрыв says something is missing («нет кадра», «нет текста»),
search the annotation's «Элементы» and «Тексты» and the register's states
for the element number and the SRS id. A Разрыв that the frame disproves
sends the frontend developer to the designer for nothing.
Orchestrator (session)
The session writes no screen file. screen-writer reads Figma and the API
and writes the files itself; there is no doc-typist on this stage.
Find the register. Missing → stop and name
ui-design— except
fromupgrade-docs(MODE as-is,upgrade-docs/references/as-is.md):
no frames, the screens come from the frontend's routes, in route order. Read its
header and both tables. Only screens with statusготово к разработке
in «Экраны» are specified; aчерновик
screen is left out and named in the close, because its frame may still
move.Check the other inputs. The product has HTTP and
documentation/api/openapi.yamlis missing → stop and nameopenapi-spec-generator. Callget_metadataon one screennodeIdfrom
the register to prove the Figma MCP answers; failure → stop with the/mcpinstruction. The SRS path comes from the register'ssources:; a
pin behind the SRS's currentversionis said in the chat (doc-versioning
→ Staleness) — the frames may lag the SRS.Pick the screens.
- No screen file exists yet → every ready screen.
- Screen files exist (an increment) → a screen is in scope when its frame
changed, its operations changed, a rule it cites changed, or the
register added it. Check each existing file against its frame:get_metadataon the screen, state and nested-stepnodeIds, and
compare element numbers, variants and node ids with the file's «Кадр»
column; compare the operations in «Метод и поля» withdocumentation/api/openapi.yaml;
read the SRS journal for the cited rule ids. A screen with no evidence
of change is not dispatched and its file is not touched. - The user named screens → those, plus nothing else.
Write the list in the chat with the reason per screen («S-3 — кадр 22:10:
новый элемент 7»), then dispatch; this is not a question.
The test cases' trace, when user test cases exist: run
python3 "<folder of this SKILL.md>/../ui-test-cases/scripts/check_trace.py" --docs documentation. Cases
on screens outside this run go toui-test-writer(MODE increment) at
once, to rewrite each broken trace from the current spec; cases on
screens in this run go to it after their files land, at the close. What
a rewrite cannot fix goes into the close as a gap.Settle the general handler once. The rows for
401,429and5xx
must read the same in every file. When a screen file exists, copy itsобщий обработчикrows. Otherwise the first writer derives them from the
register's «Общие состояния» (Сессия истекла, Слишком много запросов,
Ошибка сервера, Нет сети — each anОбщее · …frame) and their texts;
the session copies the returned rows into every later packet. A status
the app must handle globally with no row there is a Разрыв (design).Progress file, then dispatch. Before the first dispatch, create
documentation/plans/<version>/progress-screen-specs.mdperpipeline→references/progress-files.mdwith every screen in scope⏳ ждёт. A screen goes🔄 в работеwhen its writer is
dispatched,🔍 проверкаwhen its file lands, and✅ готовоwith its
element and Разрывы counts after step 6 (or🔁 возвращено: …). For
the chat, runpython3 "<folder of this SKILL.md>/../pipeline/scripts/progress_link.py" documentation/plans/<version>/progress-screen-specs.md(add--desktop
when the session has the built-in browser) and paste the line it prints
as is — before the first dispatch and after each
finished screen; after each finished screen check the context too (pipeline→references/context.md→ When to hand over). It carries the count and the link to the page that
reloads itself; a link written by hand opens the Markdown source or a
page that never refreshes.
Readexecutor-catalog. Resolvescreen-writer, write the
resolved row (screen-writer | screen-writer) and dispatch it
withreferences/writer-prompt.md, without asking which model and with nomodelon the call. One dispatch per screen; a batch of up to three only
when each is small (about eight elements or fewer, no nested step).
Dispatch one at a time, not in parallel: a parallel dispatch would need a
worktree, which starts from HEAD and misses the uncommitted register and
API, and the Figma MCP serves one reader at a time better. A missing agent
or a rejected alias is a stop per the catalog.Inspect each file on disk against the Quality gate below. Mechanical
checks first, by script — runpython3 -I "<folder of this SKILL.md>/scripts/check_spec.py" --docs documentation S-<n>
and read only what it prints: sections and frontmatter, every called
operation in the contract, every non-success status it declares with a
row in «Ошибки ответов», look words, and theобщий обработчикrows
word for word across the product's files. A look word it prints in a
recorded user decision is judged, not sent back blindly. Then by hand:
every «Кадр»nodeIdresolves (get_metadataon a sample of three, and
on every one the writer flagged). A miss sends that screen back with the list, round
after round while each round closes something; when progress stops it
is a stop with the report (pipeline→references/convergence.md). Do not edit the file
yourself.BLOCKEDwith a question only the user can answer: ask it in the chat
(executor-catalog→ Escalation), paste the answer intoANSWERSand
dispatch the same screen again. Any otherBLOCKEDis reported as is;
there is no escalation row.Close (below), then ask the gate and the next stage.
Writer (dispatched)
You write the screen files the packet names, following this skill andreferences/template.md. You cannot ask the user: what the inputs do not
decide is a Разрыв, not a guess.
Read the frames. One frame per call, never a whole page: a page-wide
read overflows the reply. When a read goes through use_figma (a script
over the file) instead of the read tools below, it returns at most 48,000
bytes — one frame (about 30 nodes) per call, ids, names and short texts
only. For each screen: get_metadata on the screen frame,
each state frame, each nested-step frame, and the S-n · Аннотация frame —
layer names (действие: …, ввод: …, шаг: M<n>, данные: …), component
variants, nodeIds. The annotation's «Элементы» table gives each layer its№ and flow; its Фокус line becomes the «Фокус» table in Экран. When the register says
Слои по действиям: нет (a designer's file left as drawn), read the
layers as they are, number elements in the user's reading order, and add
one Разрыв (design): «нет аннотации — № назначены ТЗ». Its texts come
from the text layers: get_design_context on each screen, state and
nested-step frame, every text copied character for character and markedтекст слоя with the layer's nodeId; a text only a screenshot shows (a
picture, not a layer) is a Разрыв текст — нет на кадре, not read off the
picture. Oneget_screenshot per screen, state and nested-step frame, to see what the
metadata cannot tell. The «Элементы» and «Тексты» tables must be copied verbatim: read them
with get_design_context on the annotation frame only, because a screenshot
read risks a typo. Call get_design_context on another frame only when
metadata and the screenshot cannot tell what an element is or which variant
it uses, or to read the text layers of a file without annotation (above);
it is a code-generation read and pulls assets this step does not need. When the figma-* skills are installed, follow them for these calls;
otherwise use the Figma MCP tools directly (mcp__figma__*, ormcp__plugin_dev-pipeline_figma__* from the plugin). Never write anodeId you did not read from the file.
Read the contract. In documentation/api/openapi.yaml, each operation
the screen calls: parameters, request body, response schema with its field
paths, every declared status with its error code. From the SRS: the actors
on this screen and what each may do, the UC flows (main, Alt, Exc) with
their named errors, and the rule ids behind each condition. Build the map
flow → named error → status before copying texts (What a file never contains
→ wording).
Write. Fill the template section by section, run the sync check, and
put every miss in Разрывы. On an increment, rewrite the screen file in
place: re-read its frames and operations, keep the file's path, keep № as
the annotation now gives it, remove Разрывы rows that are resolved, and
leave version, updated and the journal alone. Write only the files in
the packet. Do not commit, and do not run doc-review or pipeline.
Quality gate
- Every section of the template is present in order; Поля ввода is omitted
only on a screen with no inputs; «Загрузка и вызовы» is a numbered list,
each dependent call naming the earlier item and the field it takes, and
no diagram anywhere. - Every element of the annotation has a row with
№, behaviour per
condition, method and fields (orтекст кадра), andnodeId· variant. - Every interactive branch ends in one outcome from the closed set; every
операцияid exists in the contract (documentation/api/). - Every non-success status of every called operation has a row in «Ошибки
ответов»;общий обработчикrows match the other screen files of the
product word for word. - Every text in «» is in the frame's «Тексты» table or, in a file
without annotation, is a text layer markedтекст слояwith itsnodeId; a missing one is a Разрыв, not prose. - Every computed value is in «Производные значения» once, with its rule id
orиз API; no element row repeats a formula; no business rule the API
returns is recomputed. - No look in words, no backend internals, no global
ux-patternsbehaviour,
no column names, no restated rule text. - The eight sync checks ran; each miss is a Разрывы row with one owner, and
no «нет кадра / нет текста» row is disproved by the annotation. - Frontmatter has
version,updated,sources(SRS and API pinned aspath@<version>, the register by path) andfigma: <fileKey> / <nodeId>.
Closing
Report in the chat:
- each file written or rewritten, with its element and Разрывы counts;
- screens left out:
черновикin the register, or untouched on an increment; - Разрывы grouped by owner, with the stage that fixes them —
design→ui-design,API→openapi-spec-generator,SRS→srs-writer. This
skill fixes none of them; a fix there re-runs this stage for the affected
screens. A Разрыв with ownerAPIorSRSthat the user settles in the
chat is a changed decision: a line edit lands in that document at once,
committed with no version change, and one that needs design judgment (a
new operation) is that stage's increment (pipeline→references/decision-changes.md); - the executor rows dispatched (
screen-writer× N) and any re-dispatch; - the test cases' trace: broken before the run, rewritten, still open.
Then read pipeline and ask per "Asking before a transition": first the
gate doc-review on the files just written (one review over the set — its
verdict goes on the progress file's «Ревью ТЗ» line), then
the next stage its table names — ui-test-cases in mode write. A skipped
gate is named in the report and blocks nothing; a declined offer stops the
sitting.
References
references/template.md— the file template and a filled example. Read
before writing or checking a file.references/writer-prompt.md— the packet and report forscreen-writer.scripts/check_spec.py— the mechanical checks of step 6; the writer
runs it too before its report.executor-catalog— the Dispatch contract.doc-versioning— versioning of each screen file,open-questions.md.ux-patterns— the completeness list of states and the global behaviour
this spec does not restate.pipeline— the gate and the next stage.