Extract responsive primitives from per-app implementations into koder_web_kit + koder_kit

accepted

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#008koder_kit#030tools/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:

  1. *uplicação* 22 media queries em tools/design-gen/assets/css/base.css

    reescrevem a mesma matemática de breakpoints que outros web apps reescrevem por sua vez.

  2. *ugs de regressão por consumidor* na sessão 20260512 a KDS

    produziu overflow horizontal em 320px (catalog grid com minmax(380px, 1fr) forçando coluna > viewport; long-token sem overflow-wrap: anywhere). O Hub provavelmente tem variações sutilmente diferentes do mesmo erro.

  3. *pec drift silencioso* nada impede um app de implementar

    breakpoints inconsistentes com a spec — o responsiveness.kmd nã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_kit v0.4+).
  • Fluid typography (já coberta por specs/fonts/typography.kmd).
  • Component-specific responsive variants (cada componente do

    koder_web_kit manté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. *1 — Breakpoints canônicos.*Aceitar

    xs=360 / sm=640 / md=900 / lg=1280 / xl=1640px como Koder- canônicos. Justificativa: 360 mapeia Pixel-class Android (logical width baseline); 640 mesma do Tailwind sm; 900 entre Tailwind (768) e Bootstrap (992), próximo do Material (905); 1280 mesma do Tailwind xl; 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.*

  1. *2 — responsive.css no core do koder_web_kit.*Sempre

    carregado, 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.*

  1. *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.*

  1. *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 (containertype: 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 continua

    sendo 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 cada

    produto novo Koder.

  • specs/web-apps/responsiveness.kmd — contrato atual.
  • rfcs/design-RFC-001-koder-design-system.kmd — programa umbrella.
  • Sessão 20260512 (commits 0fd939e036, 09b9119535, +pending) —

    bugs de responsividade KDS que motivaram esta RFC.

Source: ../home/koder/dev/koder/meta/docs/stack/rfcs/design-RFC-002-responsiveness-sdk-extraction.kmd