widget-development
Build or update widgets in `app/features/widgets` using the `_template/widget` conventions (container/UI/lib/store/constants) so code stays consistent with project rules.
SKILL.mdskills/widget-development/SKILL.md
GitHubWidget Development
When to use
- Creating a new widget (copy
_template/widgetintoapp/features/widgets/<WidgetName>and rename Sample placeholders). - Updating container/UI/lib/store/constants for an existing widget while keeping template rules intact.
- Removing an existing widget and its wiring from store/actions via CLI.
Quick start
- Scaffold via CLI:
ill addWidget Header- or
ill addWidget --name Header - Add
--no-storeif the widget does not need Redux slice/store wiring.
- Copy the template folder, rename files/types/components from Sample to the widget name, and set
constants/name.tsto include the widget identifier (built onNAME_FROM_PACKAGE_JSON). - Keep export surface the same (
index.tsxre-exports store/constants/lib and exposesContainer.displayName = name); register the reducer where the feature is consumed if the widget needs store state. - Replace placeholder DTO/UI types, schemas, and data-testids; move every magic number/string into
constants/and re-export viaconstants/index.ts. - Remove via CLI when needed:
ill removeWidget Header- or
ill removeWidget --name Header
- Do not confuse:
- repo root
_template/*: scaffolding templates used byill addWidget/ill addService - widget-local
templates/*: UI decomposition pieces used by the widget itself (still pure-render, no hooks)
- repo root
File roles and guardrails
container/useContainer.ts
- Hook signature stays
useContainer: CC<UIPropertyType>; no props passed. - Comment every logical block with
// Описание: Начало/Конец; do not comment the return; update comments instead of deleting. - Naming: camelCase vars/functions, PascalCase components/types, booleans with is/has/should/can; handlers named
on***Handler; use meaningful names. - No
useCallback/useMemo(React compiler handles it). - Data flow: backend ↔ UI goes through
lib/mappers; forms useuseForm<SampleFieldsType>+ schema fromlib/schemas; types live inlib/types. - Constants in SCREAMING_SNAKE_CASE under
constants/with barrel re-export; no magic literals inline. - Query naming pattern:
Mutation naming pattern:const { data: sampleData, isLoading: isSampleLoading, isSuccess: isGetSampleSuccess, isError: isGetSampleError, refetch: refetchSample, } = useGetSampleByIdQuery({})const [ updateSampleTrigger, // or createSampleTrigger { data: sampleData, isLoading: isSampleLoading, isSuccess: isGetSampleSuccess, isError: isGetSampleError, refetch: refetchSample, }, ] = useUpdateDraftMutation() - Keep navigation params in refs to avoid stale closures; expose
isEnabled,isLoading, and mapped UI data in return.
- Hook signature stays
UI (
ui/index.tsx) and templates (templates/Sample/index.tsx)- Pure render only: no hooks/logic; no
React.memo/useCallback/useMemo. - Comment logical blocks with JSX
{/* Описание: Начало/Конец */}; keep and update existing comments. - Functions typed via
Callback<Value, ReturnType>orVoidFunction; handler namingon***Handler. - Arrays rendered via
ArrayRender;classNamecomposed withclsx. - Use
Flexcomponent instead of flex classes; preferNullable<Type>over|| null. - Forms:
UseFormControlType<SampleFieldsEnum>+<Controller/>; types fromlib/types; data flows through mappers. - Do not rename
OwnPropertyTypein templates; keep meaningful dti values (constants/data-testid.ts).
- Pure render only: no hooks/logic; no
lib/types
- Use
typealiases (no interfaces) withTypesuffix; enums withEnumsuffix. - No function type definitions here; boolean names with is/has/should/can.
- House DTO adapters (Request/Response), UI types, web-client types, and form enums/records.
- Use
lib/mappers
- Sole place for DTO ↔ UI transformations; isolate backend contract changes; return
Nullable.
- Sole place for DTO ↔ UI transformations; isolate backend contract changes; return
lib/schemas
- Yup schemas for forms; add fields keyed by enums; constants still live in
constants/.
- Yup schemas for forms; add fields keyed by enums; constants still live in
lib/utils
- Lightweight helpers that consume typed data; keep null-handling helpers simple; magic values go to
constants/.
- Lightweight helpers that consume typed data; keep null-handling helpers simple; magic values go to
store/index.ts
- Only reducers inside
createSlice; no extra functions; initial state imported fromstructure. - Do not type
initialState; constants fromconstants/; re-export actions/reducer viaActionsandReducer.
- Only reducers inside
structure/index.ts
- Holds
initialStateonly; no functions; no type declaration for the state object.
- Holds
constants/
- SCREAMING_SNAKE_CASE, meaningful names, no single-letter or cryptic abbreviations; re-export through
constants/index.ts. name.tsbuilds a widget name fromNAME_FROM_PACKAGE_JSON;data-testid.tsderivesdtifromname.
- SCREAMING_SNAKE_CASE, meaningful names, no single-letter or cryptic abbreviations; re-export through
index.tsx
ContainerwrapsUIwithuseContainer(); keep displayName set toname; re-export store/constants/lib for consumers.
Final checks
- Comments preserved/updated per template; no hooks/logic leaked into UI; no inline magic values; types/mappers/schemas/constants live in their dedicated files.
- Query/mutation naming matches patterns; data flows through mappers; form types/schemas align.
- Exports updated after renaming Sample artifacts; store reducer registered where needed.