Extract responsive primitives from per-app implementations into koder_web_kit + koder_kit
RFC: Responsiveness as SDK contract, not perapp reimplementation
Status
*ccepted*— ratificada pelo owner em 20260512. D1-D4 resolvidos (ver § Decisões abertas resolvidas). Implementação destrava em cadeia: koder_web_kit#008 → koder_kit#030 → tools/design-gen#003.
Motivação
Spec web-apps/responsiveness.kmd define a intenção (breakpoints, quando colapsar sidebar, quando empilhar verticalmente, etc.) mas cada consumidor da Stack — KDS site, Hub web, Flow web, 5+ landings de produto, eventualmente apps Flutter via koder_kit — implementa o mecanismo *eparadamente* Resultado:
- *uplicação* 22 media queries em
tools/design-gen/assets/css/base.cssreescrevem a mesma matemática de breakpoints que outros web apps reescrevem por sua vez.
- *ugs de regressão por consumidor* na sessão 2026
0512 a KDSproduziu overflow horizontal em 320px (catalog grid com
minmax(380px, 1fr)forçando coluna > viewport; long-token semoverflow-wrap: anywhere). O Hub provavelmente tem variações sutilmente diferentes do mesmo erro. - *pec drift silencioso* nada impede um app de implementar
breakpoints inconsistentes com a spec — o
responsiveness.kmdnão tem CI gate que valide. Cada PR consumidor inventa de novo.
A spec descreve * quê* a Stack precisa de *ecanismo*que encapsule responsividade da mesma forma que koder_kit/KoderSignInButton encapsula login (em vez de cada app reimplementar OAuth flow).
Proposta
Materializar responsividade em * camadas* mantendo a spec como contrato e introduzindo SDK como implementação canônica.
Camada 1: Spec atualizada (specs/web-apps/responsiveness.kmd)
Permanece. Recebe edição pequena:
- Substitui pseudo-código por *eferência ao SDK como implementação canônica*
- Adiciona seção "T1-Tn — tests obrigatórios" que cada consumidor deve
rodar (ex.: "T1: at viewport 320×800, no element has
getBoundingClientRect().right > viewport.width + 1"). Padrão dos outros specs Koder com test contract. - Sem mudanças nos breakpoints canônicos já definidos.
Camada 2: engines/sdk/koder_web_kit/css/responsive.css (novo)
CSS file exportado pelo koder_web_kit que define:
:root {
--kds-bp-xs: 360px;
--kds-bp-sm: 640px;
--kds-bp-md: 900px;
--kds-bp-lg: 1280px;
--kds-bp-xl: 1640px;
}
.kds-hide-below-md { display: none; }
@media (min-width: 900px) { .kds-hide-below-md { display: revert; } }
.kds-hide-above-md { display: revert; }
@media (min-width: 900px) { .kds-hide-above-md { display: none; } }
.kds-grid-auto {
display: grid;
gap: var(--kds-grid-gap, 16px);
grid-template-columns: repeat(
auto-fit,
minmax(min(var(--kds-grid-min, 380px), 100%), 1fr)
);
}
.kds-stack-below-md { display: flex; flex-direction: row; gap: 16px; }
@media (max-width: 899px) {
.kds-stack-below-md { flex-direction: column; }
}*ão-goals da v0.1:*
- Container queries (próxima onda —
koder_web_kitv0.4+). - Fluid typography (já coberta por
specs/fonts/typography.kmd). - Component-specific responsive variants (cada componente do
koder_web_kitmantém seu próprio CSS modular — responsive.css é apenas a *ase utilitária compartilhada*.
Camada 3: engines/sdk/koder_web_kit/js/elements/koder-stack.js (novo)
Web components puro vanilla, ~3kB cada:
<koder-stack direction-below-md="column" gap="24">
<article>…</article>
<article>…</article>
</koder-stack>
<koder-grid min-column="280" gap="16">
<article>…</article>
…
</koder-grid>Renderizados como display: grid|flex no host, sem Shadow DOM (segue o padrão do <koder-embed> shipado em 20260512 — herdar CSS do host).
Camada 4: engines/sdk/koder_kit/lib/src/responsive/ (Flutter)
class KoderBreakpoints {
static const double xs = 360;
static const double sm = 640;
static const double md = 900;
static const double lg = 1280;
static const double xl = 1640;
}
extension KoderBreakpointContext on BuildContext {
bool get isMobile => MediaQuery.sizeOf(this).width < KoderBreakpoints.md;
bool get isTablet => MediaQuery.sizeOf(this).width >= KoderBreakpoints.md
&& MediaQuery.sizeOf(this).width < KoderBreakpoints.lg;
bool get isDesktop => MediaQuery.sizeOf(this).width >= KoderBreakpoints.lg;
}
class KoderResponsiveLayout extends StatelessWidget {
final Widget Function(BuildContext) mobile, tablet, desktop;
…
}KoderSafeScaffold e demais widgets do koder_kit já consultam KoderBreakpoints internamente após esta RFC (ex.: drawer vs. sidebar permanente).
Plano de implementação
| # | Ticket | Componente | Esforço |
|---|---|---|---|
| 1 | RFC ratificada + spec atualizada | meta/docs/stack |
0.5d |
| 2 | koder_web_kit/responsive.css v0.1 + vars + 6 utility classes + tests |
engines/sdk/koder_web_kit |
1d |
| 3 | <koder-stack> + <koder-grid> web components + tests |
engines/sdk/koder_web_kit |
1-2d |
| 4 | koder_kit/responsive (breakpoints + extension + layout widget) |
engines/sdk/koder_kit |
1d |
| 5 | Migrar tools/design-gen pra consumir responsive.css (auditar 22 media queries) |
tools/design-gen |
2-3d |
| 6 | Migrar 5 landings de produto + Hub + Flow web | distribuído | 1-2 sprints |
| 7 | CI gate: assert no consumer redefines --kds-bp-* localmente |
scripted | 0.5d |
*ritério de homologação:*o bug de 320px overflow shipado em 20260512 não pode reaparecer em *enhum*consumer após Block 5 shipar.
Decisões resolvidas (ratificadas 20260512)
- *1 — Breakpoints canônicos.*Aceitar
xs=360 / sm=640 / md=900 / lg=1280 / xl=1640pxcomo Koder- canônicos. Justificativa: 360 mapeia Pixel-class Android (logical width baseline); 640 mesma do Tailwindsm; 900 entre Tailwind (768) e Bootstrap (992), próximo do Material (905); 1280 mesma do Tailwindxl; 1640 ajustado pro padrão da Stack (sidebar 248 + maxcontent 1200 + breathing room 192). NãoTailwind-puro porque a KDS já implementou essas 22 media queries com esses valores; re- auditar tudo pra alinhar com Tailwind não tem ganho concreto. Marca como "Koder-canônico, inspirado em Tailwind, ajustado pra layouts da Stack". *esolvido: aceitar valores propostos.*
- *2 —
responsive.cssno core dokoder_web_kit.*Semprecarregado, não opt-in. ~2kB minificado é abaixo de qualquer threshold racional de "vale opt-in", e o custo de "esqueci de importar → layout silenciosamente quebra" é alto. Qualquer consumer Koder vai usar pelo menos uma utility class. *esolvido: core.*
- *3 — KDS primeiro como consumer zero.*Migrar KDS antes das
landingsHubFlow é gerenciamento de risco. KDS tem 22 media queries + 5 page types — é o ambiente mais denso. Se a SDK não cobre o caso da KDS, descobrimos antes de propagar pra 7+ consumers simultaneamente. Match exato com "ship simples + iterate" do hyperscale-first. *esolvido: KDS primeiro; landings depois.*
- *4 — Container queries deferred pra v0.2.*V0.1 tem que ser
bulletproof, focado em primitives consolidadas. CQs são poderosos mas adicionam edge cases (container
type: inlinesize chains, scoping) e mantêm 8% dos usuários em fallback. Viewport-based breakpoints cobrem 100% do que a KDS precisa hoje. CQs ganham ticket dedicado em v0.2 depois que a v0.1 estiver provada em ≥2 consumers. *esolvido: diferir pra v0.2.*
Não-goals
- Substituir
specs/web-apps/responsiveness.kmd. A spec continuasendo o contrato; SDK é a implementação.
- Forçar Tailwind ou qualquer biblioteca CSS externa. As primitivas
são vanilla.
- Cobrir TV (
product/app/tv/) — outro paradigma, outro SDK(eventualmente
koder_tv_kit).
Referências
policies/reuse-first.kmd— 3-perguntas justificam SDK extraction.policies/hyperscale-first.kmd— mesmo esforço, ganho linear cadaproduto novo Koder.
specs/web-apps/responsiveness.kmd— contrato atual.rfcs/design-RFC-001-koder-design-system.kmd— programa umbrella.- Sessão 2026
0512 (commits0fd939e036,09b9119535, +pending) —bugs de responsividade KDS que motivaram esta RFC.