Skip to content

Commit 544c7a8

Browse files
committed
docs: add CONTRIBUTING.md and Ko-fi funding link
1 parent 599937a commit 544c7a8

2 files changed

Lines changed: 208 additions & 0 deletions

File tree

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ko_fi: sndrae

CONTRIBUTING.md

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Contributing
2+
3+
## Prerequisites
4+
5+
- **Node.js** 20+
6+
- **npm** (lockfile-based)
7+
- GitHub Packages auth for `@satvisorcom` scope — add to `~/.npmrc`:
8+
```
9+
//npm.pkg.github.com/:_authToken=YOUR_TOKEN
10+
@satvisorcom:registry=https://npm.pkg.github.com
11+
```
12+
13+
## Getting Started
14+
15+
```bash
16+
npm install
17+
npm run dev # Vite dev server on http://localhost:1420
18+
npm run build # tsc + vite build → dist/
19+
npm run preview # serve production build locally
20+
```
21+
22+
## Project Structure
23+
24+
| Directory | Purpose |
25+
|-----------|---------|
26+
| `src/stores/` | Svelte 5 rune-based singleton stores (`*.svelte.ts`) |
27+
| `src/ui/` | Svelte components — windows, panels, toolbar. `shared/` for reusables |
28+
| `src/scene/` | Three.js scene objects (earth, orbits, atmosphere, moon, sun, orrery) |
29+
| `src/astro/` | Pure math — SGP4 helpers, az/el, eclipse, magnitude, epoch utils |
30+
| `src/passes/` | Pass prediction (predictor class + Web Worker) |
31+
| `src/rotator/` | Rotator protocol drivers (rotctld, GS-232, EasyComm) |
32+
| `src/shaders/` | GLSL vertex/fragment shaders |
33+
| `src/feedback/` | Multi-target sensory feedback (audio, haptic, BLE devices) |
34+
| `src/data/` | TLE loading and source definitions |
35+
| `src/interaction/` | Camera controller and input |
36+
| `src/styles/` | Global CSS with all theme variables |
37+
38+
## Testing the Rotator
39+
40+
Two ways to test rotator functionality without hardware:
41+
42+
### Built-in simulator
43+
44+
The project includes a WebSocket rotator simulator that speaks rotctld protocol with simulated motor physics (acceleration, deceleration, backlash):
45+
46+
```bash
47+
npm run rotator-sim
48+
# or with a custom port:
49+
node scripts/rotator-sim.mjs 4534
50+
```
51+
52+
Connect in the app: Setup tab → Network mode → `ws://localhost:4534` (or `4533` for default).
53+
54+
### rotctld dummy rotator
55+
56+
Use Hamlib's `rotctld` with the dummy backend for a more realistic test (supports az/el limits, error codes):
57+
58+
```bash
59+
# Terminal 1: start rotctld with dummy rotator model
60+
rotctld -m 1 -vvvvv -t 1234 -C min_el=5
61+
62+
# Terminal 2: bridge WebSocket to TCP (pick one)
63+
websocat --binary ws-l:127.0.0.1:4534 tcp:127.0.0.1:1234
64+
# or
65+
websockify 4534 localhost:1234
66+
```
67+
68+
Install a bridge if needed:
69+
```bash
70+
cargo install websocat # single Rust binary
71+
# or
72+
pip install websockify # Python
73+
```
74+
75+
Connect in the app: Setup tab → Network mode → `ws://localhost:4534`.
76+
77+
Verify rotctld is responding:
78+
```bash
79+
echo "p" | nc -q1 localhost 1234
80+
# Should return two lines: azimuth and elevation
81+
```
82+
83+
### Testing with a remote rotator
84+
85+
SSH port-forward rotctld from a remote host, then bridge locally:
86+
87+
```bash
88+
ssh user@rotator-host -L 4533:localhost:4533 -N &
89+
websocat --binary ws-l:127.0.0.1:4534 tcp:127.0.0.1:4533
90+
```
91+
92+
## Desktop Builds (Tauri)
93+
94+
Requires Rust stable toolchain and system dependencies:
95+
96+
```bash
97+
# Ubuntu/Debian
98+
sudo apt install libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
99+
100+
# Build
101+
npx tauri build --bundles deb,appimage # Linux
102+
npx tauri build --bundles nsis,msi # Windows
103+
```
104+
105+
For development: `npm run tauri dev`
106+
107+
## Submitting Changes
108+
109+
1. Fork the repo and create a branch from `master`
110+
2. Make your changes and verify `npm run build` passes (TypeScript is the only automated quality gate)
111+
3. Open a pull request against `master`
112+
113+
---
114+
115+
## Conventions & Patterns
116+
117+
### Theming
118+
119+
All colors go through CSS custom properties in `src/styles/global.css` `:root`. Never hardcode hex/rgb/rgba values in `<style>` blocks or inline styles. For canvas rendering, use the `palette` object from `src/ui/shared/theme.ts`.
120+
121+
**Text color tiers:**
122+
123+
| CSS variable | Palette field | Use for |
124+
|---|---|---|
125+
| `--text` | `palette.text` | Primary text, hover states |
126+
| `--text-dim` | `palette.textDim` | Labels, secondary text |
127+
| `--text-muted` | `palette.textMuted` | Muted info |
128+
| `--text-faint` | `palette.textFaint` | Faint labels, axis text |
129+
| `--text-ghost` | `palette.textGhost` | Placeholders, section headers |
130+
131+
**Semantic colors:** `--danger`, `--warning`, `--live` for status indicators and alerts.
132+
133+
**Satellite colors:** 9-entry Pride flag palette in `src/constants.ts`. Use helpers, never inline `rgb()`:
134+
- `satColorCss(index)` — CSS/canvas fillStyle
135+
- `satColorRgba(index, alpha)` — translucent canvas
136+
- `satColorGl(index)` — WebGL/Three.js (0–1 floats)
137+
138+
### Stores
139+
140+
Class-based singletons with `$state()` fields, exported as `export const fooStore = new FooStore()`.
141+
142+
- Callback hooks (e.g., `onGraphicsChange`) registered by `App` at init, not Svelte subscriptions
143+
- localStorage persistence: `load()` at startup + immediate writes in setters, all keys prefixed `satvisor_`
144+
- Immutable updates for collections: `this.x = new Set(...)`, `this.x = { ...this.x, ... }`
145+
- Store `load()` calls go in `app.ts` init
146+
147+
### Persisted Toggles
148+
149+
Most user-facing toggles persist to localStorage. If a setting should survive page reload, follow this pattern:
150+
151+
1. Add `$state` field to the store
152+
2. Add to `loadToggles()` with a `satvisor_*` key
153+
3. Add a case to `setToggle()`
154+
4. Wire in component with `<Checkbox>` + `onchange` calling the setter
155+
156+
Never toggle state without persisting — direct assignment without a localStorage write is a bug.
157+
158+
### Shared Components
159+
160+
| Component | Use for |
161+
|-----------|---------|
162+
| `Checkbox.svelte` | All toggle checkboxes |
163+
| `DraggableWindow.svelte` | Floating windows (collision avoidance, edge snapping) |
164+
| `MobileSheet.svelte` | Mobile bottom sheets |
165+
| `InfoTip.svelte` | Hover tooltips on labels |
166+
| `Modal.svelte` | Modal dialogs |
167+
| `Slider.svelte` | Range inputs with label and value display |
168+
| `Button.svelte` | All buttons |
169+
| `icons.ts` | Inline SVG strings via `{@html ICON_FOO}` |
170+
171+
### Adding a New Window
172+
173+
Every window must support both desktop and mobile:
174+
175+
1. Add open state to `uiStore`: `myWindowOpen = $state(false)`
176+
2. Choose a unique `id` string shared by DraggableWindow and MobileSheet
177+
3. Use the snippet pattern — extract content into `{#snippet windowContent()}`:
178+
```svelte
179+
{#if uiStore.isMobile}
180+
<MobileSheet id="my-feature" title="Title" icon={myIcon}>
181+
{@render windowContent()}
182+
</MobileSheet>
183+
{:else}
184+
<DraggableWindow id="my-feature" title="Title" icon={myIcon}
185+
bind:open={uiStore.myWindowOpen} initialX={10} initialY={200}>
186+
{@render windowContent()}
187+
</DraggableWindow>
188+
{/if}
189+
```
190+
4. Register in `MobileNav.svelte` (`moreItems` array)
191+
5. Mount in `Overlay.svelte` unconditionally
192+
6. Add toolbar button in `TlePicker.svelte` and optionally a keyboard shortcut in `input-handler.ts`
193+
7. Add command palette action in `CommandPalette.svelte`
194+
195+
### Sprite Atlas
196+
197+
Satellite icons come from a sprite atlas — a horizontal strip of 256x256 sprites in `public/textures/ui/sat_sprites.png`.
198+
199+
To add a new sprite:
200+
1. Create `public/textures/ui/sprites/NN-name.svg` (256x256 viewBox, `#e3e3e3` fill)
201+
2. Add slot constant in `src/scene/sprite-config.ts`
202+
3. Update `getSpriteIndex()` for matching satellites
203+
4. Run `./scripts/generate-sprite.sh`
204+
205+
### Feedback System
206+
207+
The app routes events to audio, haptic, and BLE outputs via `feedbackStore`. Shared components (`Button`, `Checkbox`, `Slider`) already fire feedback through global DOM listeners — no per-component wiring needed. For new discrete interactions, add to `FeedbackEvent` enum and `FEEDBACK_MAP` in `src/feedback/types.ts`. For continuous interactions (drag, scrub), use `feedbackStore.fireDynamic(intensity)` with 0–1 range.

0 commit comments

Comments
 (0)