Communitygithub.com

HycJack/nsgo

Úsalo al escribir, ejecutar, probar, empaquetar o depurar una aplicación NSGo. NSGo es un motor de interfaz de usuario nativo multiplataforma con backend en Go — el puerto Go de Native Shell (NSH) — dirigido a macOS, iOS, Windows, Linux, Android y la web.

¿Qué es nsgo?

NSGo es un motor de interfaz de usuario nativo multiplataforma escrito en Go que permite crear aplicaciones con lógica de aplicación, gráficos reactivos, estilos y diseño ejecutándose todos en Go, mientras que cada plataforma renderiza con sus propias vistas nativas. Este skill se utiliza para escribir, ejecutar, probar, empaquetar y depurar aplicaciones NSGo, y se activa al crear un nuevo proyecto NSGo, usar comandos `go tool nsgo`, manipular archivos `nsgo.yaml`, trabajar con la API central (señales, vistas, texto, botones, estilo, clases Tailwind), animación y movimiento (Motion, presets, AnimatedStyle, keyframes, easing, spring, shaders, escenas 3D), aplicaciones TSX sobre goja, selectores nsgotest, incrustación o empaquetado, o código NSGo que no compile. El skill enfatiza la regla de hierro de leer la API directamente desde el módulo (`go doc`) porque NSGo es prelanzamiento y no tiene API estable ni documentación pública extensa, por lo que los identificadores recordados casi siempre son incorrectos. Soporta macOS, Windows y Linux como plataformas de desarrollo, y requiere Go 1.26.0+; su configuración añade el módulo y la herramienta con `go get -tool`, y para proyectos se usa `go tool nsgo init`. El modelo mental se basa en señales reactivas, elementos como structs con valores cero por defecto, estilos tipados o clases Tailwind, y un sistema de movimiento con dos relojes (commit y frame) para animaciones eficientes. El skill también cubre el bucle de desarrollo (`go tool nsgo dev`), construcción (`go tool nsgo build`), pruebas (`go tool nsgo test`) y errores comunes como adivinar nombres de campos, mezclar enums o malinterpretar el comportamiento del scroll.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/HycJack/nsgo-motion-lab/tree/HEAD

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace nsgo?

Un motor de UI en Go: la lógica de la aplicación, el grafo reactivo, los estilos, el diseño y el texto se ejecutan en Go; cada plataforma dibuja con sus propias vistas nativas. Las aplicaciones se escriben en Go (o TSX compilado a goja). El diseño, los nombres y el comportamiento son un puerto de NSH (Rust).

Regla de hierro: lee la API, nunca la adivines

NSGo es prelanzamiento, no tiene API estable y casi no tiene documentación pública en la web, por lo que los identificadores recordados casi siempre son incorrectos. Go falla al compilar con un solo nombre de campo incorrecto. Antes de escribir una aplicación, obtén la verdad del propio módulo:

go doc github.com/oneo-me/nsgo/core      # o: go doc -all ./core
go doc github.com/oneo-me/nsgo/base

En un checkout del repositorio, ARCHITECTURE.md, docs/architecture/*.md (25 documentos temáticos) y examples/ son autoritativos; examples/gallery ejercita todos los elementos, componentes y servicios. Si no tienes ni el módulo ni el repositorio, dilo — no sustituyas otro framework (Fyne, Gio, Wails) por la solicitud real del usuario.

Configuración

Requiere Go 1.26.0+ (go.mod dice go 1.26.0).

SODestino nativoNotas
macOS✅Cadena de herramientas completa: construye/firma cada destino con Go + Xcode
Windows✅Construcción y ejecución nativa y web
Linux✅Necesita GTK 4.14+, Pango, Cairo; WebKitGTK 6.0 y GStreamer para vistas web/vídeo

Añade el módulo y su herramienta a la aplicación una vez — go tool nsgo luego la ejecuta sin instalar:

go get -tool github.com/oneo-me/nsgo/cmd/nsgo

NSGo no publica versiones etiquetadas, por lo que el módulo se resuelve a una pseudoversión (v0.0.0-2026…-46c7d9c). Algunos mirrors regionales devuelven 404 en un módulo sin etiqueta; si go get no puede encontrarlo, fija el proxy:

GOPROXY=https://proxy.golang.org,direct GOSUMDB=off go get -tool github.com/oneo-me/nsgo/cmd/nsgo

GOSUMDB=off solo es necesario donde sum.golang.org es inalcanzable. Si trabajas desde un checkout local, apunta a él y ordena:

go mod edit -replace github.com/oneo-me/nsgo=/path/to/nsgo && go mod tidy

Crear un proyecto

mkdir contador && cd contador
go mod init contador
go get -tool github.com/oneo-me/nsgo/cmd/nsgo   # la librería y la herramienta
go tool nsgo init -name contador .              # escribe nsgo.yaml, app.go, app_test.go
go mod tidy
go tool nsgo dev .                             # lo ejecuta; reconstruye en cada guardado
go tool nsgo test .                            # sin cabeza: 2 aprobados

init necesita un nombre. go tool nsgo init solo en un directorio existente falla con "." is not an app name, porque el nombre por defecto viene del directorio — y en . eso es .. Pasa -name, o pasa un directorio a crear:

ComandoEfecto
go tool nsgo init -name Contador .escribe en el directorio actual, aplicación llamada Contador
go tool nsgo init Contadorcrea ./Contador/ con la aplicación
go tool nsgo init -template tsx Contadorcrea ./Contador/ como una aplicación TSX

init imprime su propio siguiente paso (next: go tool nsgo dev Contador) — confía en él más que en la memoria.

-template es uno de go (por defecto), tsx, mixed. El nombre de la aplicación acepta letras, dígitos, espacios, - y _; el bundle id se convierte en dev.nsgo.<nombre>. Pasa -module para que se escriba también go.mod.

PlantillaArchivos
gonsgo.yaml, app.go, app_test.go
tsxnsgo.yaml, main.go, App.tsx, app.test.tsx, tsconfig.json, .gitignore
mixedlo mismo, con App.tsx importando Go propio y un native.go

nsgo.yaml nombra la aplicación y sus destinos:

name: Hola
id: dev.oneo.nsgo.hola
version: 0.1.0
dev:
  target: native        # native | web | ios | ios_simulator | android
  hot_reload: true
  fps: false
build:
  output: dist
  targets: [macos_arm64, windows_x64, ios_simulator, android_arm64, web]

También válido: build_number, appearance, theme, i18n, icon.{apple,png}, dev.icon, android.permissions, sign.{profile,keystore}, web.host. Junto a él, resources/ contiene archivos enviados (cargados como asset:), assets/ contiene archivos solo de proyecto (iconos), locales/ contiene en.yaml, de.yaml… en ICU MessageFormat.

La aplicación que obtienes (-template go)

import (
	"fmt"

	"github.com/oneo-me/nsgo"
	"github.com/oneo-me/nsgo/base"
	"github.com/oneo-me/nsgo/core"
)

func Raiz(cx *core.Scope) core.Node {
	contador := core.NewSignal(cx, 0)
	return core.View{Style: core.Style{FlexGrow: 1, Padding: core.All(24), Gap: 16}, Children: []core.Node{
		core.Text{ContentFunc: func() string { return fmt.Sprintf("Presionado %d veces", contador.Get()) }},
		base.Button{
			Label:   "Presióname",
			OnPress: func(core.PressEvent) { contador.Update(func(n int) int { return n + 1 }) },
			Style:   core.Style{Background: core.RGB(0x0969da), CornerRadius: 8, Padding: core.Symmetric(10, 16)},
			Children: []core.Node{core.Text{Content: "Presióname", Style: core.Style{Color: core.RGB(0xffffff)}}},
		},
	}}
}

func main() { nsgo.Run(Raiz) }

nsgo.Run toma una vista raíz (func(cx *core.Scope) core.Node) o una función de aplicación (func(app *core.Scope)) que abre sus propias ventanas a través de desktop. Opciones: WindowSize(w, h), Appearance(...), HideDock(), FPS(bool), WindowMaterial(...), Fonts(paths...), Component(name, factory).

Modelo mental

  • Señales. core.NewSignal[T comparable](cx, v) → .Get(), .Set(v), .Update(fn). NewMemo, NewEffect. El grafo pertenece a la gorutina del motor y nunca se bloquea; otras gorutinas entran con Scope.Post.
  • Los elementos son structs; el valor cero es el predeterminado (como http.Server). Solo estableces lo que quieres que sea diferente.
  • Cada campo reactivo tiene un gemelo: Content + ContentFunc func() string, Style + StyleFunc, Class + ClassFunc. XFunc gana sobre X; establecer ambos causa pánico al montar. Los campos sin forma estática solo toman la función (Disabled func() bool, Selected, Expanded).
  • Componentes son cualquier tipo con Render(cx *core.Scope) core.Node; sus campos son props. core.Func adapta una función simple.
  • Regiones — Show, Each, Dynamic, Catch — reconstruyen solo su propio tramo. Each es una función genérica, no un método.
  • Estilizado: valores tipados core.Style, o clases Tailwind en Class ("flex-row gap-4 p-4 bg-white dark:bg-zinc-900"). Los componentes de base están deliberadamente sin estilo — solo comportamiento, estado y accesibilidad.

Movimiento

Dos relojes. Commit = Go vuelve a renderizar un tramo (señales, Show/Each/Dynamic). Frame = el animador de la plataforma avanza los campos AnimatedStyle y los uniforms del shader, sin commit. Todo el movimiento pertenece al reloj de frame; un valor leído durante el renderizado vuelve a renderizar el subárbol y reinicia cada animación en él.

NecesidadUsa
Un cambio de estado (hover, press, selección, redimensionar)Style.Transition — una duración, core.EaseOut, y una máscara de bits Properties; sin grafo en absoluto, y se omite bajo reduce-motion
Entrada/salida/atención/bucle estándarcore.Motion + un core.MotionPreset (38 de ellos, todos respetan reduce-motion)
Coreografía personalizada, superposición, pausas, intercambiosAnimatedStyle + grafo AnimatedNode (Timing, Spring, Interpolate, Sequence, Stagger, Loop)
Campo por píxel / infinito / 3DUniforms core.Shader como nodos — cero commits
Dibujo proceduralcore.Canvas Draw (vuelve a grabar por cambio) o d.Bind (la plataforma lo reproduce, sin regrabación)
espec := core.FadeInUp.Spec()             // o tu propio core.MotionSpec
m := core.NewMotion(cx, &spec)           // nil = construir en reposo, Play más tarde
return core.View{Style: core.Style{Animated: m.Style()}, Children: []core.Node{tarjeta()}}

Cinco reglas que valen más que la API: (1) nunca leas una señal o valor animado en el renderizado — solo closures o AnimatedStyle; (2) cambia de estilo en lugar de reconstruir; (3) una animación finita mantiene su último valor, por lo que el frame al final debe ser el legible (un bucle que vuelve al inicio se ve en blanco una vez que se estabiliza); (4) un elemento animado necesita un padre medido — una View completamente absoluta mide 0×0 y recorta todo; (5) los campos AnimatedStyle son AnimatedNodes, no números (core.Constant(1), no 1), y no tiene Width/Height/Perspective.

Lee references/animation.md antes de escribir cualquier animación: tabla de presets, números de easing/duración que funcionaron, recetas de coreografía, movimiento con shader y canvas, 3D (ray marching), pruebas, y una tabla de síntoma→causa para errores de movimiento.

Bucle de desarrollo, construcción, pruebas

TareaComando
Ejecutar, reconstruir al guardargo tool nsgo dev <dir>
Ejecutar en un navegadorgo tool nsgo dev -target web <dir>
Ejecutar en simulador/dispositivogo tool nsgo dev -target ios_simulator|android|ios <dir>
Rastrear cada commit... -trace
Paquetes de lanzamientogo tool nsgo build <dir> (-target all, -output DIR, -run, -device D)
Incrustar en una aplicación anfitriona... build -embed → XCFramework (iOS) / AAR (Android) / nsgo.js (web); los escritorios usan nsgo.NewView
Pruebasgo tool nsgo test <dir> (-run REGEXP, -junit FILE, -target macos|windows|linux|web|ios_simulator|android, -fps)
Verificar tipo TSXgo tool nsgo check <dir>
Regenerar catálogos de mensajesgo tool nsgo i18n <dir>

Las banderas anulan los valores de nsgo.yaml que nombran. build.targets y -target aceptan all, o nombres como windows_x64, macos_arm64, linux_x64, ios, web. build escribe un directorio por destino bajo -output (por defecto dist), nombrado <name>-<version>-<target>/. Un destino de prueba de escritorio se ejecuta solo en ese SO (-target windows en Windows, -target macos en una Mac); web, ios_simulator y android se ejecutan desde una Mac.

Las pruebas actúan como un usuario. nsgotest las ejecuta sin cabeza y sin ventana; -target ejecuta las mismas pruebas en una ventana real con entrada nativa y capturas de pantalla (app.Screenshot("nombre")).

func TestMain(m *testing.M) { nsgotest.Main(m) }   // da a la plataforma el hilo principal

func TestPulsar(t *testing.T) {
	app := nsgotest.Start(t, Raiz)
	app.Press("text:Presióname")
	app.Find("text:Presionado 1 veces")
}

Los selectores son los de Playwright: id:, label:, text: (una subcadena; text:"Guardar" o una cadena simple = todo el texto), role:<rol> <nombre>, view:<n>, /regexp/flags, y >> para descender. Pasos: Press, PressDown/PressUp, LongPress, Key, KeyPress, Type, Focus, Hover, Scroll, ScrollTo, Drag, Touch, Back, Resize, Settle, Advance, WaitFor, WaitForGone, Exists, Query, Texts, Dump. Lista completa: go doc github.com/oneo-me/nsgo/nsgotest.

Las aplicaciones TSX tienen la misma forma: App.tsx importa mount, signal, Text, View de "nsgo" (componentes de "nsgo/base") y termina con mount(App); main.go incrusta las fuentes y llama a nsgo.Run(compiler.App(src, "App.tsx")). Los archivos *.test.tsx se ejecutan como pruebas Go. Ver references/api.md.

Errores comunes

ErrorRealidad
Adivinar nombres de camposNSGo es prelanzamiento con casi ninguna documentación pública, por lo que los identificadores recordados son incorrectos. go doc primero — un nombre incorrecto es un error de compilación
Establecer tanto Content como ContentFuncPánico al montar. Elige uno
NewSignal(cx, []string{...})T debe ser comparable. Usa NewSignalFunc con tu propio equal
NewSignal fuera de un scopeLas señales necesitan un cx; el grafo es local a la gorutina, por lo que cx es el único manejador. Desde otra gorutina usa Scope.Post
Esperar que Button.Label rendericeLabel es el nombre accesible (lo que coinciden los selectores role:/label:). El texto visible viene de Children
Importar internal/...Las aplicaciones solo pueden importar nsgo, core, base, mobile, desktop, i18n, icons, tsx, nsgotest
go build ./... en Windows o LinuxFalla: internal/platform/apple/edge.go tiene su etiqueta //go:build darwin perdida e importa purego/objc solo de darwin. Construye tu propio paquete (go build .), o añade la etiqueta en un checkout
JustifyContent: core.AlignCenterDos enums: AlignItems toma core.AlignStart/Center/End/Stretch/Baseline, JustifyContent toma core.JustifyStart/Center/End/SpaceBetween/.... Mezclarlos es un error de compilación
core.Shader{OnMouseMove: ...}Un shader tiene OnPress, OnLayout, Style — sin manejador de movimiento del puntero. Pon OnMouseMove func(Point) / OnHoverOut func() en un core.View alrededor
Confiar solo en MustCompileShaderKage compila en tiempo de ejecución, por lo que un shader malo causa pánico al iniciar, no al construir. Prueba cada fuente con core.CompileShader e informa su error
Un Scroll que nunca se desplaza y no muestra barraScroll por defecto tiene FlexShrink: 1, pero un padre View no: una columna con solo FlexGrow: 1 crece hasta su contenido, la vista de desplazamiento hereda esa altura y nada se desborda. Dale a la columna FlexGrow: 1, FlexShrink: 1 (el flex: 1 de React Native). Afírmalo: el marco del scroll debe ser más corto que la ventana
Buscar una barra de desplazamiento permanenteEl indicador es una superposición: la plataforma nativa pinta un pulgar redondeado que se desvanece al desplazarse, se mantiene 700ms, luego se desvanece en 300ms; HideIndicator lo elimina. En la web es la barra propia del navegador (overflow-y: auto, scrollbar-width: auto)
Una superposición fijada solo con LeftUna vista posicionada absolutamente con un borde horizontal toma el ancho restante de su bloque contenedor, no su ancho de contenido: un tooltip en Left: 34 dentro de un anclaje redondo de 26pt se queda sin nada y se envuelve a un carácter por línea. Dale un Width explícito
Alfa dentro de un color interpoladoAnimatedStyle.Background alimentado por core.Interpolate con Colors: {RGBA(0xffffff, 0.12), ...} pinta como blanco casi sólido, no 12%. Usa colores opacos para rellenos interpolados y mantén alfa para Style.Background
MotionPreset.Name() como etiquetaDevuelve camelCase (fadeIn, zoomIn, breathe), no el nombre de la constante Go
Una animación "vuelve al inicio"Una señal o valor animado leído durante el renderizado, o NewAnimatedValue construido dentro del renderizado de un Dynamic: el tramo se reconstruye y los valores se reinician. Lee dentro

Skills relacionados