chore: snapshot WIP previo + flow 0008 + 7 sub-issues (0112-0119)
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1 del flow 0008 (kanban_cpp + agent_runner_api + DoD schema). Incluye: - dev/flows/0008-kanban-cpp-and-agent-workflows.md - dev/issues/0112-0119*.md (7 sub-issues) - WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,16 @@ def generate(app_md: Path, modules_root: Path, app_name: str, out_path: Path) ->
|
||||
if not isinstance(uses_modules, list):
|
||||
uses_modules = []
|
||||
|
||||
# Toda app C++ procesada por add_imgui_app enlaza fn_framework. Inyectamos
|
||||
# framework_cpp implicito si la app es C++ y no lo declara explicitamente.
|
||||
# Asi el array embebido refleja la version real del framework linkeado,
|
||||
# sin pedir a cada app.md que lo declare a mano.
|
||||
lang = str(fm.get("lang", "")).lower()
|
||||
if lang == "cpp":
|
||||
already = [str(m) for m in uses_modules]
|
||||
if not any(m.startswith("framework_") for m in already):
|
||||
uses_modules = ["framework_cpp"] + already
|
||||
|
||||
entries: list[dict] = []
|
||||
missing: list[str] = []
|
||||
for mid in uses_modules:
|
||||
@@ -107,6 +117,20 @@ def generate(app_md: Path, modules_root: Path, app_name: str, out_path: Path) ->
|
||||
else:
|
||||
lines.append("const ModuleInfo app_modules_array[1] = { { nullptr, nullptr, nullptr } };")
|
||||
lines.append("const unsigned long app_modules_count = 0;")
|
||||
|
||||
# Header badge identity — auto-derivado del bloque `icon:` del app.md.
|
||||
# Permite que el framework muestre el badge accent en viewports secundarios
|
||||
# sin tocar main.cpp. Coherente con App Hub (mismo hex que la tarjeta).
|
||||
icon_block = fm.get("icon") or {}
|
||||
accent_hex = str(icon_block.get("accent", "") or "")
|
||||
glyph_name = str(icon_block.get("phosphor", "") or "")
|
||||
lines.append(
|
||||
f'const char* const app_header_accent_hex = "{_escape_c_string(accent_hex)}";'
|
||||
)
|
||||
lines.append(
|
||||
f'const char* const app_header_glyph_name = "{_escape_c_string(glyph_name)}";'
|
||||
)
|
||||
|
||||
lines.append("} // namespace fn")
|
||||
lines.append("")
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ name: export_hub_icons
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "export_hub_icons(out_dir: str, *, size: int = 64, registry_root: str | None = None) -> dict"
|
||||
description: "Rasteriza iconos PNG para todas las apps C++/imgui del registry y los escribe en un directorio de salida. Consume registry.db para listar apps, lee icon.phosphor + icon.accent de cada app.md y usa la misma logica visual de generate_app_icon (fondo redondeado accent + glyph Phosphor blanco al 70%)."
|
||||
signature: "export_hub_icons(out_dir: str, *, size: int = 64, registry_root: str | None = None, style: str = 'fill_white') -> dict"
|
||||
description: "Rasteriza iconos PNG para todas las apps C++/imgui del registry y los escribe en un directorio de salida. Consume registry.db para listar apps, lee icon.phosphor + icon.accent de cada app.md y usa la logica visual de generate_app_icon. Param `style` soporta 'fill_white' (default, glyph blanco solido), 'adaptive_duotone' y 'white_duotone' (glyph duotone Phosphor — alineado con generate_app_icon)."
|
||||
tags: ["hub", "launcher", "icons", "suite", "png", "phosphor", "imgui", "cpp-windows"]
|
||||
params:
|
||||
- name: out_dir
|
||||
@@ -15,6 +15,8 @@ params:
|
||||
desc: "Lado del cuadrado del PNG en pixels. Default 64. El hub launcher usa 64 para las tarjetas."
|
||||
- name: registry_root
|
||||
desc: "Ruta a la raiz del fn_registry. Si es None usa FN_REGISTRY_ROOT env o /home/lucas/fn_registry."
|
||||
- name: style
|
||||
desc: "Estilo del icono igual que generate_app_icon: 'fill_white' (default), 'adaptive_duotone' o 'white_duotone'. CLI: `--style <valor>`."
|
||||
output: "dict con ok=True, count=N (PNGs escritos), out_dir (ruta absoluta), skipped (lista de {name, reason} para apps omitidas)."
|
||||
uses_functions:
|
||||
- generate_app_icon_py_infra
|
||||
|
||||
@@ -15,11 +15,19 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from infra.generate_app_icon import _find_registry_root, _make_icon_image
|
||||
from infra.generate_app_icon import (
|
||||
_find_registry_root,
|
||||
_make_icon_image,
|
||||
_make_icon_image_adaptive,
|
||||
_make_icon_image_duotone,
|
||||
WHITE_DUOTONE_GLYPH,
|
||||
)
|
||||
|
||||
DEFAULT_PHOSPHOR = "app-window"
|
||||
DEFAULT_ACCENT = "#64748b"
|
||||
DEFAULT_SIZE = 64
|
||||
DEFAULT_STYLE = "fill_white"
|
||||
VALID_STYLES = ("fill_white", "adaptive_duotone", "white_duotone")
|
||||
|
||||
|
||||
def _read_frontmatter(md_path: Path) -> dict[str, Any]:
|
||||
@@ -46,6 +54,7 @@ def export_hub_icons(
|
||||
*,
|
||||
size: int = DEFAULT_SIZE,
|
||||
registry_root: str | None = None,
|
||||
style: str = DEFAULT_STYLE,
|
||||
) -> dict:
|
||||
"""Rasteriza iconos PNG para todas las apps C++/imgui del registry.
|
||||
|
||||
@@ -76,6 +85,9 @@ def export_hub_icons(
|
||||
]
|
||||
}
|
||||
"""
|
||||
if style not in VALID_STYLES:
|
||||
raise ValueError(f"style invalido: {style!r}. Valores: {' | '.join(VALID_STYLES)}")
|
||||
|
||||
root = _find_registry_root(registry_root)
|
||||
|
||||
db_path = root / "registry.db"
|
||||
@@ -128,17 +140,30 @@ def export_hub_icons(
|
||||
phosphor_name = DEFAULT_PHOSPHOR
|
||||
accent_hex = DEFAULT_ACCENT
|
||||
|
||||
# --- Localizar SVG Phosphor ---
|
||||
svg_path = phosphor_assets / "fill" / f"{phosphor_name}-fill.svg"
|
||||
if not svg_path.exists():
|
||||
msg = f"Phosphor SVG not found: {svg_path}"
|
||||
print(f"[export_hub_icons] SKIP {app_name}: {msg}", file=sys.stderr)
|
||||
skipped.append({"name": app_name, "reason": msg})
|
||||
continue
|
||||
# --- Localizar SVG Phosphor (duotone preferido si style lo necesita) ---
|
||||
svg_fill = phosphor_assets / "fill" / f"{phosphor_name}-fill.svg"
|
||||
svg_duo = phosphor_assets / "duotone" / f"{phosphor_name}-duotone.svg"
|
||||
if style == "fill_white":
|
||||
if not svg_fill.exists():
|
||||
msg = f"Phosphor SVG not found: {svg_fill}"
|
||||
print(f"[export_hub_icons] SKIP {app_name}: {msg}", file=sys.stderr)
|
||||
skipped.append({"name": app_name, "reason": msg})
|
||||
continue
|
||||
else:
|
||||
if not svg_duo.exists() and not svg_fill.exists():
|
||||
msg = f"Phosphor SVG not found: {svg_duo} ni {svg_fill}"
|
||||
print(f"[export_hub_icons] SKIP {app_name}: {msg}", file=sys.stderr)
|
||||
skipped.append({"name": app_name, "reason": msg})
|
||||
continue
|
||||
|
||||
# --- Rasterizar y guardar PNG ---
|
||||
try:
|
||||
img = _make_icon_image(svg_path, accent_hex, size)
|
||||
if style == "fill_white":
|
||||
img = _make_icon_image(svg_fill, accent_hex, size)
|
||||
elif style == "adaptive_duotone":
|
||||
img = _make_icon_image_adaptive(phosphor_name, accent_hex, size, phosphor_assets)
|
||||
else: # white_duotone
|
||||
img = _make_icon_image_duotone(phosphor_name, accent_hex, size, phosphor_assets, WHITE_DUOTONE_GLYPH)
|
||||
png_out = out_path / f"{app_name}.png"
|
||||
img.save(str(png_out), format="PNG")
|
||||
count += 1
|
||||
@@ -175,11 +200,18 @@ if __name__ == "__main__":
|
||||
default=None,
|
||||
help="Path to fn_registry root (default: FN_REGISTRY_ROOT env or /home/lucas/fn_registry)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--style",
|
||||
default=DEFAULT_STYLE,
|
||||
choices=VALID_STYLES,
|
||||
help=f"Estilo del icono (default {DEFAULT_STYLE})",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
result = export_hub_icons(
|
||||
args.out_dir,
|
||||
size=args.size,
|
||||
registry_root=args.registry_root,
|
||||
style=args.style,
|
||||
)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
@@ -3,10 +3,10 @@ name: generate_app_icon
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
version: "1.2.0"
|
||||
purity: impure
|
||||
signature: "generate_app_icon(phosphor_icon_name: str, accent_hex: str, out_ico_path: str, *, weight: str = 'fill', sizes: list[int] = None, phosphor_root: str = None) -> str"
|
||||
description: "Rasteriza un icono Phosphor SVG sobre un fondo redondeado del color accent y exporta un .ico multi-resolucion (default 16,24,32,48,64,128,256). Devuelve el path absoluto del .ico escrito. El glyph se renderiza en blanco al 70% del canvas sobre fondo con esquinas redondeadas al 16%."
|
||||
signature: "generate_app_icon(phosphor_icon_name: str, accent_hex: str, out_ico_path: str, *, weight: str = 'fill', sizes: list[int] = None, phosphor_root: str = None, style: str = 'fill_white') -> str"
|
||||
description: "Rasteriza un icono Phosphor SVG sobre un fondo redondeado del color accent y exporta un .ico multi-resolucion (default 16,24,32,48,64,128,256). Soporta tres estilos via param `style`: 'fill_white' (default, glyph blanco solido), 'adaptive_duotone' (glyph duotone con tono claro u oscuro segun luminancia del accent) y 'white_duotone' (glyph duotone phosphor con fill blanco — el path bg al opacity=0.2 deja translucir el accent dando segundo tono suave). Devuelve el path absoluto del .ico escrito."
|
||||
tags: [cpp-windows, icon, windows, phosphor, ico, pillow, cairosvg]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
@@ -27,6 +27,8 @@ params:
|
||||
desc: "Lista de resoluciones a incluir. Default [16,24,32,48,64,128,256]. Cada tamano se renderiza independientemente para crispness."
|
||||
- name: phosphor_root
|
||||
desc: "Carpeta raiz de assets phosphor-core (contiene subdirs fill/, regular/, etc.). Default: <registry_root>/sources/phosphor-core/assets."
|
||||
- name: style
|
||||
desc: "Estilo de render. 'fill_white' (default, glyph blanco solido — backwards-compat). 'adaptive_duotone' (glyph duotone Phosphor con tono claro #f4f4f5 si luminancia(accent)<0.5, tono oscuro #0f0f12 si >=0.5; el path opacity=0.2 da el segundo tono). 'white_duotone' (glyph duotone Phosphor con fill #ffffff; path opacity=0.2 deja translucir el accent dando segundo tono suave; legible en cualquier accent claro u oscuro)."
|
||||
output: "Ruta absoluta (str) del archivo .ico generado y escrito a disco."
|
||||
tested: false
|
||||
tests: []
|
||||
@@ -77,4 +79,5 @@ Cuando una app C++ del registry necesita un `.ico` de Windows para distinguirse
|
||||
|
||||
## Capability growth log
|
||||
|
||||
*(sin cambios desde v1.0.0)*
|
||||
- v1.2.0 (2026-05-18) — añade `style="white_duotone"`. Glyph duotone Phosphor con fill blanco; path bg al opacity=0.2 deja translucir el accent. Mas suave que `fill_white`, mismo punch que `adaptive_duotone` pero sin condicional.
|
||||
- v1.1.0 (2026-05-17) — añade param `style="adaptive_duotone"`. Glyph duotone con tono claro/oscuro adaptativo segun luminancia del accent (Rec.601). Compatibilidad total con `style="fill_white"` por default.
|
||||
|
||||
@@ -54,6 +54,24 @@ def _hex_to_rgb(h: str) -> tuple[int, int, int]:
|
||||
return tuple(int(h[i: i + 2], 16) for i in (0, 2, 4))
|
||||
|
||||
|
||||
def _luminance(accent_hex: str) -> float:
|
||||
"""Luminancia percibida Rec.601 normalizada [0,1]."""
|
||||
r, g, b = _hex_to_rgb(accent_hex)
|
||||
return (0.299 * r + 0.587 * g + 0.114 * b) / 255.0
|
||||
|
||||
|
||||
def _render_glyph_colored(svg_path: Path, size: int, fill: str) -> Image.Image:
|
||||
"""Renderiza un SVG Phosphor reemplazando currentColor por `fill`."""
|
||||
svg = svg_path.read_text(encoding="utf-8")
|
||||
svg = svg.replace('fill="currentColor"', f'fill="{fill}"')
|
||||
png_bytes = cairosvg.svg2png(
|
||||
bytestring=svg.encode("utf-8"),
|
||||
output_width=size,
|
||||
output_height=size,
|
||||
)
|
||||
return Image.open(io.BytesIO(png_bytes)).convert("RGBA")
|
||||
|
||||
|
||||
def _render_glyph_white(svg_path: Path, size: int) -> Image.Image:
|
||||
"""Renderiza el SVG Phosphor como glyph blanco sobre fondo transparente.
|
||||
|
||||
@@ -105,6 +123,59 @@ def _make_icon_image(svg_path: Path, accent_hex: str, size: int) -> Image.Image:
|
||||
return canvas
|
||||
|
||||
|
||||
ADAPTIVE_LUMINANCE_THRESHOLD = 0.5
|
||||
ADAPTIVE_DARK_GLYPH = "#0f0f12"
|
||||
ADAPTIVE_LIGHT_GLYPH = "#f4f4f5"
|
||||
WHITE_DUOTONE_GLYPH = "#ffffff"
|
||||
|
||||
|
||||
def _make_icon_image_duotone(
|
||||
phosphor_icon_name: str,
|
||||
accent_hex: str,
|
||||
size: int,
|
||||
phosphor_assets: Path,
|
||||
glyph_color: str,
|
||||
) -> Image.Image:
|
||||
"""Compone bg accent + glyph duotone Phosphor con `glyph_color` como fill.
|
||||
|
||||
Usa la variante `duotone` de Phosphor cuando exista (el path con `opacity=0.2`
|
||||
crea automaticamente el segundo tono atenuado al mezclarse con el accent).
|
||||
Fallback a `fill` si no hay duotone.
|
||||
"""
|
||||
bg_color = _hex_to_rgb(accent_hex) + (255,)
|
||||
canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
radius = max(2, size // 6)
|
||||
draw.rounded_rectangle([(0, 0), (size - 1, size - 1)], radius=radius, fill=bg_color)
|
||||
|
||||
glyph_size = int(size * 0.7)
|
||||
if glyph_size < 8:
|
||||
glyph_size = max(8, size - 2)
|
||||
|
||||
svg_duo = phosphor_assets / "duotone" / f"{phosphor_icon_name}-duotone.svg"
|
||||
svg_fill = phosphor_assets / "fill" / f"{phosphor_icon_name}-fill.svg"
|
||||
svg_src = svg_duo if svg_duo.exists() else svg_fill
|
||||
if not svg_src.exists():
|
||||
raise FileNotFoundError(f"Icono Phosphor no encontrado: {svg_src}")
|
||||
|
||||
glyph = _render_glyph_colored(svg_src, glyph_size, glyph_color)
|
||||
off = ((size - glyph_size) // 2, (size - glyph_size) // 2)
|
||||
canvas.alpha_composite(glyph, dest=off)
|
||||
return canvas
|
||||
|
||||
|
||||
def _make_icon_image_adaptive(
|
||||
phosphor_icon_name: str,
|
||||
accent_hex: str,
|
||||
size: int,
|
||||
phosphor_assets: Path,
|
||||
threshold: float = ADAPTIVE_LUMINANCE_THRESHOLD,
|
||||
) -> Image.Image:
|
||||
"""Adaptive duotone: tono claro/oscuro segun luminancia del accent."""
|
||||
glyph_color = ADAPTIVE_DARK_GLYPH if _luminance(accent_hex) >= threshold else ADAPTIVE_LIGHT_GLYPH
|
||||
return _make_icon_image_duotone(phosphor_icon_name, accent_hex, size, phosphor_assets, glyph_color)
|
||||
|
||||
|
||||
def generate_app_icon(
|
||||
phosphor_icon_name: str,
|
||||
accent_hex: str,
|
||||
@@ -113,6 +184,7 @@ def generate_app_icon(
|
||||
weight: str = "fill",
|
||||
sizes: list[int] = None,
|
||||
phosphor_root: str = None,
|
||||
style: str = "fill_white",
|
||||
) -> str:
|
||||
"""Genera un icono .ico multi-resolucion a partir de un SVG Phosphor.
|
||||
|
||||
@@ -137,17 +209,32 @@ def generate_app_icon(
|
||||
phosphor_root: Ruta a la carpeta raiz de assets de phosphor-core
|
||||
(la que contiene subdirectorios "fill", "regular", etc.).
|
||||
Default: <registry_root>/sources/phosphor-core/assets.
|
||||
style: Estilo de render. Valores:
|
||||
- "fill_white" (default, backwards-compat): fondo accent + glyph
|
||||
blanco solido usando `weight`.
|
||||
- "adaptive_duotone": fondo accent + glyph duotone con tono claro/
|
||||
oscuro elegido por luminancia del accent. Glyph oscuro
|
||||
`#0f0f12` si L>=0.5; glyph claro `#f4f4f5` si L<0.5. Usa
|
||||
variante `duotone` de Phosphor (el path con opacity=0.2 da el
|
||||
segundo tono). Fallback a `fill` si no hay duotone.
|
||||
- "white_duotone": fondo accent + glyph duotone Phosphor con fill
|
||||
`#ffffff`. Path bg al opacity=0.2 deja translucir el accent y
|
||||
produce un segundo tono mas suave. Legible en cualquier accent
|
||||
(claro u oscuro). Fallback a `fill` si no hay duotone.
|
||||
|
||||
Returns:
|
||||
Ruta absoluta del archivo .ico generado.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: Si el SVG del icono no existe en phosphor_root.
|
||||
ValueError: Si accent_hex no tiene el formato "#RRGGBB".
|
||||
ValueError: Si accent_hex no tiene el formato "#RRGGBB" o style invalido.
|
||||
"""
|
||||
if sizes is None:
|
||||
sizes = DEFAULT_SIZES
|
||||
|
||||
if style not in ("fill_white", "adaptive_duotone", "white_duotone"):
|
||||
raise ValueError(f"style invalido: {style!r}. Valores: fill_white | adaptive_duotone | white_duotone")
|
||||
|
||||
# Resolver raiz de phosphor
|
||||
if phosphor_root is None:
|
||||
_root = _find_registry_root()
|
||||
@@ -155,22 +242,39 @@ def generate_app_icon(
|
||||
else:
|
||||
phosphor_assets = Path(phosphor_root)
|
||||
|
||||
svg_file = phosphor_assets / weight / f"{phosphor_icon_name}-{weight}.svg"
|
||||
if not svg_file.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Icono Phosphor no encontrado: {svg_file}\n"
|
||||
f"Asegurate de que sources/phosphor-core/ existe. Si no:\n"
|
||||
f" git clone --depth=1 https://github.com/phosphor-icons/core.git "
|
||||
f"sources/phosphor-core"
|
||||
)
|
||||
|
||||
# Validar formato del color
|
||||
# Validar formato del color (antes de tocar disco)
|
||||
h = accent_hex.lstrip("#")
|
||||
if len(h) != 6:
|
||||
raise ValueError(f"accent_hex debe tener formato #RRGGBB, recibido: {accent_hex!r}")
|
||||
|
||||
# Renderizar cada resolucion individualmente para crispness en tamanos pequeños
|
||||
images = {s: _make_icon_image(svg_file, accent_hex, s) for s in sorted(sizes, reverse=True)}
|
||||
if style in ("adaptive_duotone", "white_duotone"):
|
||||
svg_duo = phosphor_assets / "duotone" / f"{phosphor_icon_name}-duotone.svg"
|
||||
svg_fill = phosphor_assets / "fill" / f"{phosphor_icon_name}-fill.svg"
|
||||
if not svg_duo.exists() and not svg_fill.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Icono Phosphor no encontrado: {svg_duo} ni {svg_fill}\n"
|
||||
f"Asegurate de que sources/phosphor-core/ existe."
|
||||
)
|
||||
if style == "adaptive_duotone":
|
||||
images = {
|
||||
s: _make_icon_image_adaptive(phosphor_icon_name, accent_hex, s, phosphor_assets)
|
||||
for s in sorted(sizes, reverse=True)
|
||||
}
|
||||
else: # white_duotone
|
||||
images = {
|
||||
s: _make_icon_image_duotone(phosphor_icon_name, accent_hex, s, phosphor_assets, WHITE_DUOTONE_GLYPH)
|
||||
for s in sorted(sizes, reverse=True)
|
||||
}
|
||||
else:
|
||||
svg_file = phosphor_assets / weight / f"{phosphor_icon_name}-{weight}.svg"
|
||||
if not svg_file.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Icono Phosphor no encontrado: {svg_file}\n"
|
||||
f"Asegurate de que sources/phosphor-core/ existe. Si no:\n"
|
||||
f" git clone --depth=1 https://github.com/phosphor-icons/core.git "
|
||||
f"sources/phosphor-core"
|
||||
)
|
||||
images = {s: _make_icon_image(svg_file, accent_hex, s) for s in sorted(sizes, reverse=True)}
|
||||
|
||||
out = Path(out_ico_path)
|
||||
if not out.is_absolute():
|
||||
|
||||
@@ -3,10 +3,10 @@ name: regenerate_app_icons
|
||||
kind: pipeline
|
||||
lang: py
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
version: "1.2.0"
|
||||
purity: impure
|
||||
signature: "def regenerate_app_icons(only: list[str] | None = None) -> dict"
|
||||
description: "Escanea todas las apps C++ del registry, lee el bloque `icon: {phosphor, accent}` de cada app.md y regenera el appicon.ico via generate_app_icon. Reemplaza el script ad-hoc dev/gen_app_icons.py."
|
||||
signature: "def regenerate_app_icons(only: list[str] | None = None, style: str = 'fill_white') -> dict"
|
||||
description: "Escanea todas las apps C++ del registry, lee el bloque `icon: {phosphor, accent}` de cada app.md y regenera el appicon.ico via generate_app_icon. Soporta param `style` ('fill_white' default | 'adaptive_duotone' | 'white_duotone' bg accent + glyph duotone Phosphor con fill blanco). CLI flags: `--adaptive`, `--white`, o `--style=<valor>`. Reemplaza el script ad-hoc dev/gen_app_icons.py."
|
||||
tags: [cpp-windows, icon, phosphor, batch]
|
||||
uses_functions: [generate_app_icon_py_infra]
|
||||
uses_types: []
|
||||
@@ -17,6 +17,8 @@ imports: [os, sys, pathlib, typing, yaml]
|
||||
params:
|
||||
- name: only
|
||||
desc: "Lista opcional de nombres de app (campo `name` del frontmatter) a procesar. Si None, regenera todas las apps C++ con icon: declarado."
|
||||
- name: style
|
||||
desc: "'fill_white' (default, glyph blanco), 'adaptive_duotone' (bg accent + duotone con tono claro/oscuro adaptativo) o 'white_duotone' (bg accent + duotone Phosphor blanco). CLI: `--adaptive`, `--white`, o `--style=<valor>`."
|
||||
output: "dict {ok: [name], skipped: [{name, reason}], failed: [{name, error}]}"
|
||||
tested: false
|
||||
tests: []
|
||||
@@ -27,14 +29,14 @@ file_path: "python/functions/pipelines/regenerate_app_icons.py"
|
||||
## Ejemplo
|
||||
|
||||
```bash
|
||||
# Regenerar todas las apps C++ con icon: declarado
|
||||
# Regenerar todas las apps C++ con icon: declarado (estilo clasico)
|
||||
./fn run regenerate_app_icons
|
||||
|
||||
# Estilo adaptive_duotone (dark/light glyph segun luminancia del accent)
|
||||
python/.venv/bin/python3 python/functions/pipelines/regenerate_app_icons.py --adaptive
|
||||
|
||||
# Solo una app
|
||||
./fn run regenerate_app_icons chart_demo
|
||||
|
||||
# Varias apps
|
||||
./fn run regenerate_app_icons chart_demo registry_dashboard
|
||||
```
|
||||
|
||||
```python
|
||||
|
||||
@@ -45,12 +45,18 @@ def _iter_cpp_app_mds(root: Path):
|
||||
yield md, fm
|
||||
|
||||
|
||||
def regenerate_app_icons(only: Optional[list[str]] = None) -> dict:
|
||||
def regenerate_app_icons(
|
||||
only: Optional[list[str]] = None,
|
||||
style: str = "fill_white",
|
||||
) -> dict:
|
||||
"""Recorre apps C++ con bloque icon: en su frontmatter y regenera appicon.ico.
|
||||
|
||||
Args:
|
||||
only: Lista opcional de nombres de app a filtrar (campo `name`). Si None,
|
||||
procesa todas las apps C++ con `icon:` declarado.
|
||||
style: Estilo del icono. "fill_white" (default, glyph blanco) o
|
||||
"adaptive_duotone" (glyph duotone con tono claro/oscuro segun
|
||||
luminancia del accent). Ver generate_app_icon.
|
||||
|
||||
Returns:
|
||||
dict con keys: ok (list[str]), skipped (list[dict]), failed (list[dict]).
|
||||
@@ -77,6 +83,7 @@ def regenerate_app_icons(only: Optional[list[str]] = None) -> dict:
|
||||
phosphor_icon_name=phosphor,
|
||||
accent_hex=accent,
|
||||
out_ico_path=str(out_ico),
|
||||
style=style,
|
||||
)
|
||||
ok.append(name)
|
||||
except Exception as e:
|
||||
@@ -86,8 +93,20 @@ def regenerate_app_icons(only: Optional[list[str]] = None) -> dict:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
only = sys.argv[1:] or None
|
||||
result = regenerate_app_icons(only=only)
|
||||
args = sys.argv[1:]
|
||||
style = "fill_white"
|
||||
only = []
|
||||
for a in args:
|
||||
if a.startswith("--style="):
|
||||
style = a.split("=", 1)[1]
|
||||
elif a == "--adaptive":
|
||||
style = "adaptive_duotone"
|
||||
elif a == "--white":
|
||||
style = "white_duotone"
|
||||
else:
|
||||
only.append(a)
|
||||
only = only or None
|
||||
result = regenerate_app_icons(only=only, style=style)
|
||||
for name in result["ok"]:
|
||||
print(f"OK {name}")
|
||||
for s in result["skipped"]:
|
||||
|
||||
Reference in New Issue
Block a user