@hexah/create-skin 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,7 +38,7 @@ localStorage.setItem('hexah.skinRemote', JSON.stringify({
38
38
  entry: 'https://<rand>.trycloudflare.com/remoteEntry.js',
39
39
  template: 'sdkremote',
40
40
  screens: { report: './ReportScreen' },
41
- apiVersion: '0.2.0',
41
+ apiVersion: '0.11.0',
42
42
  }))
43
43
  ```
44
44
 
@@ -46,13 +46,113 @@ Odśwież stronę, w **Ustawieniach Gry** wybierz motyw **„SDK Remote (zdalna
46
46
  ekran zgłoszeń — zobaczysz swój `ReportScreen` na realnych danych. Po każdej zmianie w `src/`
47
47
  odśwież stronę (remote przebuduje się przez HMR).
48
48
 
49
+ ## Budowanie motywu — co masz do dyspozycji
50
+
51
+ Importuj **wyłącznie** z `@hexah/skin-sdk` (+ `react` i `@mui/*` jako współdzielone singletony
52
+ hosta). Nie masz dostępu do kodu gry — host dostarcza w runtime read-modele danych, usługi
53
+ platformy, prymitywy UI i ciężkie sloty. Kontrakt jest wersjonowany (`SKIN_API_VERSION`,
54
+ obecnie **0.11.0**); deklarowane `apiVersion` musi mieć ten sam major i minor ≤ host.
55
+
56
+ ### Trzy sposoby na skórkę
57
+
58
+ 1. **Pojedynczy ekran** — komponent zarejestrowany pod kluczem z `SCREEN_KEYS`. W
59
+ `rspack.config.js` wystawiasz go (`exposes`), w configu `localStorage` mapujesz na klucz
60
+ (`screens: { report: './ReportScreen' }`). Host renderuje go w miejscu danej podstrony.
61
+ 2. **Rama całej strony (layout/chrome)** — komponent dostaje `children` (aktywny ekran) i owija
62
+ go własnym paskiem/nawigacją/stopką. Wystawiasz np. `./SkinFrame` i podajesz w configu
63
+ `layout: './SkinFrame'`. Możesz dać samą ramę, same ekrany, albo jedno i drugie. Tutorial:
64
+ [`docs/skin-sdk/tutorial-frame.md`](https://github.com/Kroniki-Fallathanu/hexah/blob/develop/docs/skin-sdk/tutorial-frame.md).
65
+ 3. **Rozmieszczanie slotów** — ciężkie fragmenty z logiką/danymi (np. mapa świata, statystyki
66
+ zgłoszeń) renderuje host; Ty pobierasz je gotowe przez `useSlots()` i tylko ustawiasz w layoutcie.
67
+
68
+ ### Konwencja read-modeli
69
+
70
+ Każdy read-model to hook zwracający obiekt metod. Metoda przyjmuje **jeden obiekt opcji** z
71
+ polem `callback` (odbiera odpowiedź socketu) — pozostałe pola lecą do hosta jako `data`:
72
+
73
+ ```jsx
74
+ const reports = useReports()
75
+ reports.list({ page: 1, pageSize: 10, callback: (ret) => {
76
+ if (ret?.error) return
77
+ setIssues(ret.issues) // kształt zwrotki zależy od metody
78
+ }})
79
+ ```
80
+
81
+ ### Hooki danych (jeden na podstronę menu)
82
+
83
+ | Hook | Podstrona | Metody |
84
+ | --- | --- | --- |
85
+ | `useArticles` | Terminal / Aktualności / Kurier | `list`, `getBySlug`, `recordView`, `markKurierRead`, `listComments`, `createComment`, `toggleReaction` |
86
+ | `useGptThreads` | Harold (GPT) | `list`, `get`, `create`, `remove`, `sendMessage` |
87
+ | `useTales` | Operacje (opowieści) | `list`, `search`, `get`, `getFavorites`, `getPreferences`, `togglePreference`, `join`, `leave`, `toggleSubscribe`, `checkSubscribe` |
88
+ | `useInn` | Mesa (karczma) | `listTales`, `getMembersCount` |
89
+ | `useTown` | Metropolia (budynki) | `bankSend`, `innRest`, `innBuyTravelerKit`, `healerPlantsList`, `healerHeal`, `voltarToolsList`, `voltarBuy`, `hallTypes`, `hallPriceList`, `hallBuy`, `workshopPlansList`, `workshopPlanGet`, `workshopCraftItem`, `forgePlansList`, `forgePlanGet`, `forgeCraftMaterial`, `workPerform` |
90
+ | `useGuild` | Frakcje (gildie) | `create`, `join`, `list`, `search`, `get`, `sendCrabs`, `headquartersRestInfo`, `headquartersRest`, `leave`, `getPermissions`, `specialActions`, `getProperties`, `hexmapPopulationTotal`, `hexmapMappingActivityMonth`, `provinceInfo`, `publicStats`, `hexmapPopulations`, `hexmapPopulationsByMember`, `warehouseGet`, `warehouseDeposit`, `warehouseYardWithdraw`, `materialsGet`, `materialsDonate`, `materialsDistribute` |
91
+ | `useFamily` | Dynastie (rody) | `create`, `list`, `get`, `getProperties`, `choseName`, `invite`, `acceptInvitation`, `removeMember`, `editMember`, `editSpouse`, `transferOwner`, `setPermission`, `changeDescription`, `npcAccessGrant`, `npcAccessRevoke`, `npcAccessList`, `depositCrabs`, `depositInfluencePoints` |
92
+ | `useVillages` | Kolonie (osady) | `found`, `takeAbandoned`, `get`, `list`, `buildCreate`, `buildCost`, `buildUpgrade`, `buildDemolish`, `buildMove`, `buildResetAll`, `depositCrabs`, `withdrawCrabs`, `sendResourcesToGuild`, `abandon`, `rebellionResolve`, `taxesSet`, `governorAssign`, `techniqueList`, `techniqueUpgrade`, `hexDecorationsUpdate`, `nameUpdate` |
93
+ | `useShop` | Zaopatrzenie (sklep) | `getOffers`, `getItems`, `buyItem`, `startPayment` |
94
+ | `useRanking` | Notowania (rankingi) | `ranking`, `rankingExplorer` |
95
+ | `useAccountProfile` | Profil (konto) | `getAccountInfo`, `getBlockedList`, `blockUser`, `unblockUser`, `linkDiscord`, `getGlobalStats` |
96
+ | `useSettings` | Konfiguracja | `updateSetting`, `setInvisibleOnline`, `updateNewsletterSubscription` |
97
+ | `useReports` | Zgłoszenia (bugtracker) | `list`, `markAllRead`, `setIssueNotification` |
98
+
99
+ ### Read-modele stanu (czytają aktywny stan, bez argumentów)
100
+
101
+ `useShard()` · `useShardUser()` · `useCharacter()` · `useAccount()` · `usePageData()` ·
102
+ `useOnlineCharacters()` — zwracają bieżące dane świata/postaci/konta (lub `null`, gdy brak).
103
+
104
+ ### Usługi platformy
105
+
106
+ - `useSnackbar()` → `{ notify }` — `notify([{ severity, message }])` pokazuje powiadomienie hosta.
107
+ - `useDialog()` → `{ open, close }` — modal hosta (`open(config)` / `close()`).
108
+ - `useGamePageShell({ title })` — ustawia tytuł/powłokę strony (wołaj w `useMemo`, jak w przykładzie).
109
+
110
+ ### Prymitywy UI (motywowalne komponenty MUI hosta)
111
+
112
+ `PageBox` (kontener strony, pełna wysokość) · `HexahCharacterSection` · `AngularPanel` ·
113
+ `StandardButton` (tekst ≤ 16 znaków) · `RoundAvatar` · `DataList` (lista wierszy z opisem/akcją) ·
114
+ `HexahChip` · `HexahPagination` + `HexahPaginationItem`. Poza nimi używaj zwykłego `@mui/material`
115
+ (`Box`, `Typography`, …) — to też instancja hosta. **Importuj z barrela**
116
+ (`import { Box, Typography } from '@mui/material'`), **nie z subpath** (`@mui/material/Box`) — host
117
+ współdzieli jako singleton tylko barrel, więc subpath nie zbuduje się w skórce.
118
+
119
+ ### Sloty (host renderuje, skórka rozmieszcza)
120
+
121
+ ```jsx
122
+ import { useSlots, SLOT_KEYS } from '@hexah/skin-sdk'
123
+ const slots = useSlots()
124
+ const WorldMap = slots[SLOT_KEYS.WORLD_MAP]
125
+ return WorldMap ? <WorldMap /> : null // łagodna degradacja, gdy host nie dostarcza slotu
126
+ ```
127
+
128
+ Zarejestrowane: `WORLD_MAP` (Mapa Sektora — mapa świata + ruiny), `ISSUE_STATS` (statystyki
129
+ zgłoszeń). Zarezerwowane (jeszcze bez komponentu): `GAME_PAGE`, `ISSUE_RICH_CONTENT`,
130
+ `ARTICLE_COMMENTS`, `ARTICLE_ISSUE_GRID`.
131
+
132
+ ### Stałe i utile
133
+
134
+ - `SCREEN_KEYS` — klucze ekranów: `ARTICLE`, `KURIER`, `KURIER_REDAKCJA`, `KURIER_SKRZYNKA`,
135
+ `GPT_LIST`, `GPT_THREAD`, `REPORT`, `REPORT_DETAIL`, `ARTICLE_DETAIL`.
136
+ - `SLOT_KEYS` — klucze slotów (jak wyżej). `DEFAULT_TEMPLATE` · `SKIN_API_VERSION`.
137
+ - Utile nawigacji ekranu zgłoszeń (czyste funkcje): `buildReportListHref`, `buildReportDetailHref`,
138
+ `buildReportBackHref`, `parseReportListPage`, `parseReportStateFilter`, `parseReportTypeFilter`,
139
+ `parseReportMineFilter`, `parseReportPlannedFilter`; style chipów: `getStateChipSx`,
140
+ `getIssueTypeChipSx`.
141
+
142
+ > Pełna mapa „podstrona menu → hook/slot" i decyzje projektowe:
143
+ > [`docs/skin-sdk/data-hooks.md`](https://github.com/Kroniki-Fallathanu/hexah/blob/develop/docs/skin-sdk/data-hooks.md).
144
+
49
145
  ## Co generuje
50
146
 
51
147
  Gotowy projekt skórki:
52
148
 
53
- - `rspack.config.js` — `ModuleFederationPlugin` wystawiający `./ReportScreen`, paczki
54
- współdzielone z hostem (`import: false`), dev server na `:3001` (CORS + `allowedHosts`).
55
- - `src/ReportScreen.jsx` — przykładowy ekran zbudowany na `@hexah/skin-sdk`.
149
+ - `rspack.config.js` — `ModuleFederationPlugin` wystawiający ramę i ekrany, paczki współdzielone
150
+ z hostem (`import: false`), dev server na `:3001` (CORS + `allowedHosts`).
151
+ - `src/SkinFrame.jsx`, `src/ReportScreen.jsx`, `src/ArticleScreen.jsx`, `src/GptListScreen.jsx`
152
+ gotowe wzorce (rama całej strony + ekrany) na `@hexah/skin-sdk`.
153
+ - `src/examples/` — referencja: wywołania pozostałych read-modeli danych i slotów (do kopiowania).
154
+ - **`AGENTS.md` + `CLAUDE.md`** — zasady i granice dla asystentów AI (import tylko z SDK, brak
155
+ dostępu do kodu/backendu gry, współdzielone singletony). AI czyta je automatycznie.
56
156
  - `package.json`, `.gitignore`, `README.md` z instrukcją podłączenia do środowiska testowego.
57
157
 
58
158
  Nazwa kontenera Module Federation jest automatycznie sanityzowana z nazwy katalogu do
package/index.js CHANGED
@@ -45,11 +45,13 @@ function main(argv) {
45
45
  console.log(
46
46
  " entry: 'http://localhost:3001/remoteEntry.js', template: 'sdkremote',",
47
47
  );
48
- console.log(" screens: { report: './ReportScreen' }, apiVersion: '0.2.0' }))");
48
+ console.log(" screens: { report: './ReportScreen' }, apiVersion: '0.11.0' }))");
49
49
  console.log("Zdeployowany test (https) wymaga publicznego URL — odpal tunel:");
50
50
  console.log(" npx cloudflared tunnel --url http://localhost:3001 # → https://<rand>.trycloudflare.com");
51
51
  console.log("i użyj tego URL w entry + hexah.skinRemoteAllowedOrigins. Patrz README.");
52
52
  console.log("Następnie w Ustawieniach Gry wybierz motyw „SDK Remote (zdalna skórka)\".");
53
+ console.log("");
54
+ console.log("Używasz AI (Claude/Cursor/Copilot)? Zasady i granice skórki są w AGENTS.md — wskaż je asystentowi.");
53
55
  return 0;
54
56
  }
55
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexah/create-skin",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Scaffolder skórki Hexah (zdalny kontener Module Federation, harness dev z HMR).",
5
5
  "bin": {
6
6
  "create-hexah-skin": "index.js"
@@ -0,0 +1,98 @@
1
+ # AGENTS.md — zasady dla asystentów AI (skórka „__SKIN_NAME__")
2
+
3
+ Ten plik czyta asystent AI (Claude Code, Cursor, Copilot i in.) pracujący w tym repozytorium.
4
+ Trzymaj się go ściśle — wyznacza **twarde granice** skórki Hexah. Jeśli prośba użytkownika
5
+ łamie te zasady, powiedz to wprost zamiast obchodzić regułę.
6
+
7
+ ## Czym jest ten projekt
8
+
9
+ To **skórka (motyw) gry Hexah** zbudowana jako **zdalny kontener Module Federation** (rspack).
10
+ Nie jest samodzielną aplikacją: ładuje ją działający host Hexah w runtime i renderuje Twoje
11
+ komponenty wewnątrz gry. Budujesz **wyłącznie** na publicznym kontrakcie `@hexah/skin-sdk` —
12
+ **nie masz i nie będziesz mieć dostępu do kodu gry (hosta)**.
13
+
14
+ ## Twarde granice (nie do złamania)
15
+
16
+ 1. **Importuj tylko z `@hexah/skin-sdk`** (oraz `react`, `react-dom`, `@mui/*`, `@emotion/*`,
17
+ `jotai`). **Nigdy** nie importuj z wnętrza gry (`@/...`, `frontend/...`, ścieżki hosta) —
18
+ takie moduły nie istnieją w buildzie skórki. Nie wymyślaj eksportów z SDK: jeśli czegoś nie
19
+ ma w `@hexah/skin-sdk`, to nie jest dostępne.
20
+ 2. **Nie pakuj współdzielonych bibliotek.** `react`/`react-dom`/`jotai`/`@mui/*`/`@emotion/*`/
21
+ `@hexah/skin-sdk` są w `rspack.config.js` jako `shared` z `import: false` (singletony hosta).
22
+ Nie zmieniaj tego na bundlowanie — dwa Reacty/store'y rozsypią aplikację.
23
+ 3. **Nie obchodź backendu gry.** Dane bierz **wyłącznie** z read-modeli SDK (`useReports`,
24
+ `useArticles`, …). Zakaz: własny `fetch`/`axios`/WebSocket do API gry, własny klient socket,
25
+ czytanie cookie/tokenów sesji, scrapowanie globalnego stanu.
26
+ 4. **Nie zaglądaj do wnętrza slotów.** Ciężkie fragmenty (mapa, statystyki) dostajesz jako
27
+ gotowe komponenty przez `useSlots()` — renderuje je host. Nie próbuj odtwarzać ich logiki ani
28
+ sięgać po ich dane.
29
+ 5. **`apiVersion` zgodne z hostem** (ten sam major, minor ≤ host). Nie podbijaj go „na zapas".
30
+ 6. **Zero sekretów w repo** (klucze, tokeny). Skórka jest publiczna i ładowana w przeglądarce.
31
+ 7. **Nie zakładaj nawigacji/motywu hosta.** Linki to zwykłe publiczne trasy `/game/*`. Routing,
32
+ motyw (kolory/spacing) i powłokę kontroluje host — używaj tokenów MUI (`sx`, `theme`), nie
33
+ hardkoduj kolorów/pikseli.
34
+
35
+ ## Powierzchnia, na której wolno budować
36
+
37
+ - **Read-modele danych** (jeden hook na podstronę menu): `useArticles`, `useGptThreads`,
38
+ `useTales`, `useInn`, `useTown`, `useGuild`, `useFamily`, `useVillages`, `useShop`,
39
+ `useRanking`, `useAccountProfile`, `useSettings`, `useReports`.
40
+ - **Read-modele stanu**: `useShard`, `useShardUser`, `useCharacter`, `useAccount`, `usePageData`,
41
+ `useOnlineCharacters`.
42
+ - **Usługi platformy**: `useSnackbar` (`{ notify }`), `useDialog` (`{ open, close }`),
43
+ `useGamePageShell({ title })`.
44
+ - **Prymitywy UI**: `PageBox`, `HexahCharacterSection`, `AngularPanel`, `StandardButton`
45
+ (tekst ≤ 16 znaków), `RoundAvatar`, `DataList`, `HexahChip`, `HexahPagination`,
46
+ `HexahPaginationItem`. Poza nimi zwykłe `@mui/material` — **importuj z barrela**
47
+ (`import { Box, Typography } from '@mui/material'`), **nie z subpath** (`@mui/material/Box`):
48
+ host współdzieli jako singleton tylko barrel `@mui/material`, więc subpath się nie zbuduje
49
+ („Module not found").
50
+ - **Sloty**: `useSlots()` + `SLOT_KEYS` (`WORLD_MAP`, `ISSUE_STATS`, …).
51
+ - **Stałe**: `SCREEN_KEYS`, `SLOT_KEYS`, `DEFAULT_TEMPLATE`, `SKIN_API_VERSION`.
52
+
53
+ > Pełna lista metod każdego hooka i opis API: README scaffolderu `@hexah/create-skin`
54
+ > (sekcja „Budowanie motywu — co masz do dyspozycji") oraz README paczki `@hexah/skin-sdk`.
55
+ > Najpierw sprawdź te dokumenty — nie zgaduj sygnatur.
56
+
57
+ ## Konwencja read-modeli
58
+
59
+ Hook zwraca obiekt metod. Metoda przyjmuje **jeden obiekt opcji** z polem `callback`; pozostałe
60
+ pola lecą do hosta jako `data`. Kształt zwrotki (`ret`) zależy od metody — najczęściej
61
+ `{ results, pagination }` albo `{ error }`. Zawsze obsłuż `ret?.error` (np. przez `useSnackbar`).
62
+
63
+ ```jsx
64
+ const articles = useArticles()
65
+ articles.list({ page: 1, pageSize: 10, callback: (ret) => {
66
+ if (ret?.error) { notify([{ severity: 'error', message: ret.error }]); return }
67
+ setItems(ret.results || [])
68
+ }})
69
+ ```
70
+
71
+ ## Trzy sposoby budowania skórki
72
+
73
+ 1. **Ekran** — komponent pod kluczem z `SCREEN_KEYS`. Wystaw w `rspack.config.js` → `exposes`,
74
+ zmapuj w configu `screens: { report: './ReportScreen' }`. Przykłady: `src/ReportScreen.jsx`,
75
+ `src/ArticleScreen.jsx`, `src/GptListScreen.jsx`.
76
+ 2. **Rama całej strony (chrome)** — komponent dostaje `children` (aktywny ekran) i owija go
77
+ paskiem/nawigacją/stopką. Wystaw `./SkinFrame`, podaj `layout: './SkinFrame'`. Przykład:
78
+ `src/SkinFrame.jsx`.
79
+ 3. **Rozmieszczanie slotów** — `const Map = useSlots()[SLOT_KEYS.WORLD_MAP]; return Map ? <Map/> : null`.
80
+ Zawsze degraduj łagodnie, gdy slot jest `undefined`. Przykłady: `src/examples/slots.jsx`.
81
+
82
+ Wzorce wszystkich pozostałych read-modeli: `src/examples/dataHooks.jsx` (referencja do kopiowania).
83
+
84
+ ## Workflow i weryfikacja
85
+
86
+ - Dev: `npm run dev` (rspack serve, HMR) → `remoteEntry.js` na `:3001`.
87
+ - Test w grze: wystaw przez tunel (cloudflared), zautoryzuj origin i wskaż remote przez
88
+ `localStorage` na środowisku testowym — instrukcja w `README.md`.
89
+ - Po zmianach upewnij się, że build przechodzi i import-checki trzymają granice z tego pliku.
90
+ - Nie dodawaj zależności runtime, które dublują współdzielone singletony hosta.
91
+
92
+ ## Czego NIE robić (skrót)
93
+
94
+ - ❌ import z kodu gry / „magiczne" eksporty SDK, których nie ma w dokumentacji
95
+ - ❌ własny dostęp do backendu/socketów/sesji gry
96
+ - ❌ bundlowanie react/jotai/@mui/@emotion/@hexah/skin-sdk
97
+ - ❌ hardkodowane kolory/piksele zamiast tokenów MUI
98
+ - ❌ sekrety w repo; podbijanie `apiVersion` ponad hosta
@@ -0,0 +1,6 @@
1
+ # CLAUDE.md
2
+
3
+ Zasady i granice tego projektu (skórka Hexah) są w **[`AGENTS.md`](./AGENTS.md)** — przeczytaj je
4
+ przed jakąkolwiek zmianą. W skrócie: importuj wyłącznie z `@hexah/skin-sdk` (+ React/MUI), nie
5
+ sięgaj po kod gry ani jej backend, nie pakuj współdzielonych singletonów hosta. Pełna powierzchnia
6
+ API: README scaffolderu `@hexah/create-skin` i README paczki `@hexah/skin-sdk`.
@@ -4,6 +4,10 @@ Skórka Hexah zbudowana jako **zdalny kontener Module Federation**. Renderuje si
4
4
  aplikacji Hexah na środowisku testowym — budujesz ją wyłącznie na publicznym SDK
5
5
  `@hexah/skin-sdk`, bez dostępu do kodu gry.
6
6
 
7
+ > **Pracujesz z AI (Claude, Cursor, Copilot…)?** Zasady i granice skórki są w
8
+ > [`AGENTS.md`](./AGENTS.md) (Claude czyta też `CLAUDE.md`). Wskaż ten plik asystentowi na starcie —
9
+ > pilnuje, że skórka importuje tylko z `@hexah/skin-sdk` i nie sięga po kod ani backend gry.
10
+
7
11
  ## Start
8
12
 
9
13
  ```bash
@@ -31,22 +35,44 @@ localStorage.setItem('hexah.skinRemote', JSON.stringify({
31
35
  name: '__SKIN_FEDERATION_NAME__',
32
36
  entry: 'https://<rand>.trycloudflare.com/remoteEntry.js',
33
37
  template: 'sdkremote',
34
- screens: { report: './ReportScreen' },
35
- apiVersion: '0.2.0',
38
+ layout: './SkinFrame', // rama całej strony (chrome) — opcjonalna
39
+ screens: { // ekrany pod kluczami SCREEN_KEYS — opcjonalne
40
+ report: './ReportScreen',
41
+ article: './ArticleScreen',
42
+ gptList: './GptListScreen',
43
+ },
44
+ apiVersion: '0.11.0',
36
45
  }))
37
46
  ```
38
47
 
39
- Odśwież, w **Ustawieniach Gry** wybierz motyw **„SDK Remote (zdalna skórka)"** i wejdź na ekran
40
- zgłoszeń zobaczysz swój `ReportScreen` na realnych danych. HMR pokazuje zmiany w `src/` po
41
- odświeżeniu (i ponownym pobraniu remote'a).
48
+ Odśwież, w **Ustawieniach Gry** wybierz motyw **„SDK Remote (zdalna skórka)"**. `SkinFrame` owinie
49
+ wszystkie ekrany pod tym motywem, a `ReportScreen`/`ArticleScreen`/`GptListScreen` podmienią
50
+ odpowiednie podstrony — na realnych danych. HMR pokazuje zmiany w `src/` po odświeżeniu (i ponownym
51
+ pobraniu remote'a). Możesz podać samą `layout`, same `screens`, albo jedno i drugie.
42
52
 
43
53
  > Adres tunelu zmienia się po każdym restarcie cloudflared — wklej wtedy nowy URL w oba wpisy
44
54
  > `localStorage`.
45
55
 
46
56
  ## Co możesz edytować
47
57
 
48
- - `src/ReportScreen.jsx` Twój ekran. Importuj wyłącznie z `@hexah/skin-sdk` (+ React/MUI).
49
- - `rspack.config.js` → `exposes` — dołóż kolejne ekrany (klucze z `SCREEN_KEYS`).
58
+ Gotowe przykłady w `src/` (wystawione w `rspack.config.js` `exposes`):
59
+
60
+ - `src/SkinFrame.jsx` — **rama całej strony** (chrome): górny pasek + nawigacja + stopka, na
61
+ read-modelach stanu (`useCharacter`, `useShard`, `useShardUser`, `useOnlineCharacters`).
62
+ - `src/ReportScreen.jsx` — ekran **Zgłoszeń** (`useReports` + `DataList`/`HexahChip`).
63
+ - `src/ArticleScreen.jsx` — ekran **Aktualności** (`useArticles`).
64
+ - `src/GptListScreen.jsx` — ekran **Harolda** (`useGptThreads` + `StandardButton`).
65
+
66
+ Referencja (nie wystawiona — kopiuj do własnych ekranów):
67
+
68
+ - `src/examples/dataHooks.jsx` — wywołanie **każdego** pozostałego read-modelu danych
69
+ (`useTales`, `useInn`, `useTown`, `useGuild`, `useFamily`, `useVillages`, `useShop`,
70
+ `useRanking`, `useAccountProfile`, `useSettings`).
71
+ - `src/examples/slots.jsx` — sloty hosta przez `useSlots()` (`WORLD_MAP`, `ISSUE_STATS`).
72
+
73
+ Dokładasz własne ekrany w `rspack.config.js` → `exposes` i mapujesz je w configu na klucze
74
+ `SCREEN_KEYS`. **Pełna referencja** (trzy sposoby budowania, tabela hooków z metodami, UI, sloty)
75
+ jest w README scaffolderu `@hexah/create-skin`: <https://www.npmjs.com/package/@hexah/create-skin>.
50
76
 
51
77
  ## Zasady
52
78
 
@@ -66,9 +92,13 @@ npm install @hexah/skin-sdk
66
92
  ```
67
93
 
68
94
  - Paczka na npm (pełna lista hooków i kontrakt): <https://www.npmjs.com/package/@hexah/skin-sdk>
69
- - Hooki danych per strona gry (`useArticles`, `useTales`, `useTown`, `useGuild`, `useVillages`,
70
- `useReports`, `useSettings`, `useShop`, `useAccountProfile`, `useGptThreads`), stan
71
- (`useShard`/`useCharacter`/…), usługi (`useSnackbar`/`useDialog`), sloty (`useSlots`) i
72
- prymitywy UI opis w README paczki.
95
+ - Hooki danych per strona gry (`useArticles`, `useTales`, `useInn`, `useTown`, `useGuild`,
96
+ `useFamily`, `useVillages`, `useReports`, `useSettings`, `useShop`, `useAccountProfile`,
97
+ `useRanking`, `useGptThreads`), stan (`useShard`/`useCharacter`/…), usługi
98
+ (`useSnackbar`/`useDialog`), sloty (`useSlots`) i prymitywy UI.
99
+
100
+ **Pełna referencja motywu** (trzy sposoby budowania, tabela hooków z metodami, prymitywy UI,
101
+ sloty, stałe) jest w README scaffolderu `@hexah/create-skin` → sekcja **„Budowanie motywu — co
102
+ masz do dyspozycji"**: <https://www.npmjs.com/package/@hexah/create-skin>.
73
103
 
74
104
  Importuj **wyłącznie** z `@hexah/skin-sdk`. Zadeklarowane `apiVersion` musi być zgodne z hostem.
@@ -9,7 +9,7 @@
9
9
  "serve": "rspack serve"
10
10
  },
11
11
  "dependencies": {
12
- "@hexah/skin-sdk": "^0.2.0"
12
+ "@hexah/skin-sdk": "^0.11.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@module-federation/enhanced": "^0.9.0",
@@ -39,7 +39,15 @@ module.exports = {
39
39
  new ModuleFederationPlugin({
40
40
  name: "__SKIN_FEDERATION_NAME__",
41
41
  filename: "remoteEntry.js",
42
- exposes: { "./ReportScreen": "./src/ReportScreen.jsx" },
42
+ // Wystawione moduły skórki. Ekrany mapujesz w configu na klucze `SCREEN_KEYS`
43
+ // (`screens: { report: './ReportScreen', article: './ArticleScreen', ... }`), a ramę
44
+ // całej strony na pole `layout` (`layout: './SkinFrame'`). Dorzuć kolejne wg potrzeb.
45
+ exposes: {
46
+ "./SkinFrame": "./src/SkinFrame.jsx",
47
+ "./ReportScreen": "./src/ReportScreen.jsx",
48
+ "./ArticleScreen": "./src/ArticleScreen.jsx",
49
+ "./GptListScreen": "./src/GptListScreen.jsx",
50
+ },
43
51
  shared: {
44
52
  react: singleton("^19"),
45
53
  "react-dom": singleton("^19"),
@@ -48,7 +56,7 @@ module.exports = {
48
56
  "@emotion/styled": singleton("^11"),
49
57
  "@mui/material": singleton("^7"),
50
58
  "@mui/material/styles": singleton("^7"),
51
- "@hexah/skin-sdk": singleton("^0.2"),
59
+ "@hexah/skin-sdk": singleton("^0.11"),
52
60
  },
53
61
  }),
54
62
  ],
@@ -0,0 +1,73 @@
1
+ /**
2
+ * PRZYKŁAD ekranu Aktualności — rejestrowany pod `SCREEN_KEYS.ARTICLE`
3
+ * (config: `screens: { article: './ArticleScreen' }`).
4
+ *
5
+ * Pokazuje read-model `useArticles` (lista artykułów) + prymitywy UI hosta (`PageBox`,
6
+ * `DataList`, `HexahChip`) + `useGamePageShell` (tytuł strony). Konwencja read-modeli:
7
+ * metoda dostaje obiekt opcji z `callback`, reszta pól leci do hosta jako `data`.
8
+ */
9
+ import { useEffect, useMemo, useState } from "react";
10
+ import { Box, Typography } from "@mui/material";
11
+ import {
12
+ PageBox,
13
+ DataList,
14
+ HexahChip,
15
+ useArticles,
16
+ useSnackbar,
17
+ useGamePageShell,
18
+ } from "@hexah/skin-sdk";
19
+
20
+ export default function ArticleScreen() {
21
+ const articles = useArticles();
22
+ const { notify } = useSnackbar();
23
+ const [items, setItems] = useState([]);
24
+ useGamePageShell(useMemo(() => ({ title: "Aktualności (__SKIN_NAME__)" }), []));
25
+
26
+ useEffect(() => {
27
+ articles.list({
28
+ page: 1,
29
+ pageSize: 10,
30
+ callback: (ret) => {
31
+ if (ret?.error) {
32
+ notify([{ severity: "error", message: ret.error }]);
33
+ return;
34
+ }
35
+ setItems(ret.results || []);
36
+ },
37
+ });
38
+ }, [articles, notify]);
39
+
40
+ const rows = useMemo(
41
+ () =>
42
+ items.map((article) => ({
43
+ id: article.id,
44
+ title: article.title,
45
+ icon: article.cover?.url,
46
+ chips: article.pinned ? (
47
+ <HexahChip label="Przypięte" size="small" />
48
+ ) : null,
49
+ // Nawigacja do szczegółu to zwykły link do publicznej trasy gry.
50
+ rowClick: () => {
51
+ window.location.href = `/game/article/${article.slug}`;
52
+ },
53
+ })),
54
+ [items],
55
+ );
56
+
57
+ return (
58
+ <PageBox>
59
+ <Box sx={{ px: { xs: 2, sm: 3 }, pt: 2 }}>
60
+ <Typography variant="body2" sx={{ mb: 2, opacity: 0.8 }}>
61
+ Aktualności na realnych danych hosta (read-model `useArticles`).
62
+ </Typography>
63
+ {rows.length === 0 ? (
64
+ <Typography color="primary" sx={{ py: 2 }}>
65
+ Brak artykułów do wyświetlenia.
66
+ </Typography>
67
+ ) : (
68
+ <DataList itemsList={rows} />
69
+ )}
70
+ </Box>
71
+ </PageBox>
72
+ );
73
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * PRZYKŁAD ekranu wątków Harolda (GPT) — rejestrowany pod `SCREEN_KEYS.GPT_LIST`
3
+ * (config: `screens: { gptList: './GptListScreen' }`).
4
+ *
5
+ * Pokazuje read-model `useGptThreads` (lista + tworzenie wątku), usługę `useDialog`
6
+ * (modal hosta) oraz prymityw `StandardButton` (tekst ≤ 16 znaków).
7
+ */
8
+ import { useEffect, useMemo, useState } from "react";
9
+ import { Box, Typography } from "@mui/material";
10
+ import {
11
+ PageBox,
12
+ DataList,
13
+ StandardButton,
14
+ useGptThreads,
15
+ useSnackbar,
16
+ useGamePageShell,
17
+ } from "@hexah/skin-sdk";
18
+
19
+ export default function GptListScreen() {
20
+ const gpt = useGptThreads();
21
+ const { notify } = useSnackbar();
22
+ const [threads, setThreads] = useState([]);
23
+ useGamePageShell(useMemo(() => ({ title: "Harold (__SKIN_NAME__)" }), []));
24
+
25
+ const reload = useMemo(
26
+ () => () =>
27
+ gpt.list({
28
+ page: 1,
29
+ pageSize: 20,
30
+ callback: (ret) => {
31
+ if (ret?.error) {
32
+ notify([{ severity: "error", message: ret.error }]);
33
+ return;
34
+ }
35
+ setThreads(ret.results || ret.threads || []);
36
+ },
37
+ }),
38
+ [gpt, notify],
39
+ );
40
+
41
+ useEffect(() => {
42
+ reload();
43
+ }, [reload]);
44
+
45
+ const startThread = () => {
46
+ gpt.create({
47
+ message: "Witaj, Haroldzie!",
48
+ callback: (ret) => {
49
+ if (ret?.error) {
50
+ notify([{ severity: "error", message: ret.error }]);
51
+ return;
52
+ }
53
+ reload();
54
+ },
55
+ });
56
+ };
57
+
58
+ const rows = useMemo(
59
+ () =>
60
+ threads.map((thread) => ({
61
+ id: thread.id,
62
+ title: thread.title || `Wątek #${thread.id}`,
63
+ rowClick: () => {
64
+ window.location.href = `/game/harold/${thread.id}`;
65
+ },
66
+ })),
67
+ [threads],
68
+ );
69
+
70
+ return (
71
+ <PageBox>
72
+ <Box sx={{ px: { xs: 2, sm: 3 }, pt: 2 }}>
73
+ <Box sx={{ display: "flex", justifyContent: "flex-end", mb: 2 }}>
74
+ <StandardButton onClick={startThread}>Nowy wątek</StandardButton>
75
+ </Box>
76
+ {rows.length === 0 ? (
77
+ <Typography color="primary" sx={{ py: 2 }}>
78
+ Brak wątków — załóż pierwszy.
79
+ </Typography>
80
+ ) : (
81
+ <DataList itemsList={rows} />
82
+ )}
83
+ </Box>
84
+ </PageBox>
85
+ );
86
+ }
@@ -6,8 +6,7 @@
6
6
  * (`PageBox`, `DataList`, `HexahChip`) dostarcza host; tu masz tylko ich kontrakt.
7
7
  */
8
8
  import { useEffect, useMemo, useState } from "react";
9
- import Box from "@mui/material/Box";
10
- import Typography from "@mui/material/Typography";
9
+ import { Box, Typography } from "@mui/material";
11
10
  import {
12
11
  PageBox,
13
12
  DataList,
@@ -0,0 +1,83 @@
1
+ /**
2
+ * PRZYKŁAD: skórka jako RAMA CAŁEJ STRONY (chrome). Host renderuje aktywny ekran i podaje go
3
+ * jako `children` — skórka owija go własnym górnym paskiem, nawigacją i stopką. To podmienia
4
+ * cały layout gry pod Twoim szablonem (rejestrowane przez config z polem `layout: './SkinFrame'`).
5
+ *
6
+ * Pokazuje read-modele STANU: `useCharacter`, `useShard`, `useShardUser`, `useOnlineCharacters`.
7
+ * Importuj WYŁĄCZNIE z `@hexah/skin-sdk` (+ React/MUI). Nawigacja to zwykłe linki do tras
8
+ * `/game/*` (publiczne adresy) — nie masz dostępu do wewnętrznego routingu gry.
9
+ */
10
+ import { Box, Typography } from "@mui/material";
11
+ import {
12
+ useCharacter,
13
+ useShard,
14
+ useShardUser,
15
+ useOnlineCharacters,
16
+ } from "@hexah/skin-sdk";
17
+
18
+ const NAV = [
19
+ ["/game/article", "Aktualności"],
20
+ ["/game/tale?filter=MojeAktywne", "Operacje"],
21
+ ["/game/town", "Metropolia"],
22
+ ["/game/guild", "Frakcje"],
23
+ ["/game/map", "Mapa"],
24
+ ["/game/rank", "Notowania"],
25
+ ];
26
+
27
+ export default function SkinFrame({ children }) {
28
+ const character = useCharacter();
29
+ const shard = useShard();
30
+ const shardUser = useShardUser();
31
+ const online = useOnlineCharacters();
32
+
33
+ return (
34
+ <Box sx={{ display: "flex", flexDirection: "column", minHeight: "100vh" }}>
35
+ <Box
36
+ component="header"
37
+ sx={{
38
+ px: 3,
39
+ py: 1.5,
40
+ display: "flex",
41
+ alignItems: "center",
42
+ gap: 2,
43
+ flexWrap: "wrap",
44
+ bgcolor: "background.paper",
45
+ borderBottom: "2px solid",
46
+ borderColor: "primary.main",
47
+ }}
48
+ >
49
+ <Typography variant="h6" sx={{ fontWeight: 700 }}>
50
+ __SKIN_NAME__
51
+ </Typography>
52
+ <Box component="nav" sx={{ display: "flex", gap: 2, flex: 1, flexWrap: "wrap" }}>
53
+ {NAV.map(([href, label]) => (
54
+ <Typography
55
+ key={href}
56
+ component="a"
57
+ href={href}
58
+ variant="body2"
59
+ sx={{ textDecoration: "none", color: "text.primary" }}
60
+ >
61
+ {label}
62
+ </Typography>
63
+ ))}
64
+ </Box>
65
+ <Typography variant="caption" color="text.secondary">
66
+ {character?.name ?? "—"}
67
+ {shardUser?.user?.username ? ` (${shardUser.user.username})` : ""} ·{" "}
68
+ {shard?.name ?? ""} · online: {Array.isArray(online) ? online.length : 0}
69
+ </Typography>
70
+ </Box>
71
+
72
+ <Box component="main" sx={{ flex: 1 }}>
73
+ {children}
74
+ </Box>
75
+
76
+ <Box component="footer" sx={{ px: 3, py: 1, textAlign: "center", opacity: 0.6 }}>
77
+ <Typography variant="caption">
78
+ „__SKIN_NAME__" — rama całej strony (Skin SDK)
79
+ </Typography>
80
+ </Box>
81
+ </Box>
82
+ );
83
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * KSIĄŻKA KUCHARSKA read-modeli danych (REFERENCJA — nieeksportowana w `rspack.config.js`).
3
+ *
4
+ * Te komponenty nie trafiają do builda, dopóki nie dodasz ich do `exposes` albo nie zaimportujesz
5
+ * z ekranu/ramy. Pokazują wywołanie KAŻDEGO pozostałego read-modelu danych z `@hexah/skin-sdk`.
6
+ * Wzorzec stały: hook zwraca obiekt metod; metoda dostaje obiekt opcji z `callback`, a pozostałe
7
+ * pola lecą do hosta jako `data`. Kształt zwrotki (`ret`) zależy od metody — najczęściej
8
+ * `{ results, pagination }` lub `{ error }`.
9
+ *
10
+ * Pełna lista metod każdego hooka: README scaffolderu `@hexah/create-skin`
11
+ * (sekcja „Budowanie motywu — co masz do dyspozycji").
12
+ */
13
+ import { useEffect, useState } from "react";
14
+ import { Typography } from "@mui/material";
15
+ import {
16
+ useTales,
17
+ useInn,
18
+ useTown,
19
+ useGuild,
20
+ useFamily,
21
+ useVillages,
22
+ useShop,
23
+ useRanking,
24
+ useAccountProfile,
25
+ useSettings,
26
+ } from "@hexah/skin-sdk";
27
+
28
+ /** Operacje (opowieści): lista moich aktywnych. */
29
+ export function TalesExample() {
30
+ const tales = useTales();
31
+ const [list, setList] = useState([]);
32
+ useEffect(() => {
33
+ tales.list({
34
+ filter: "MojeAktywne",
35
+ page: 1,
36
+ callback: (ret) => setList(ret?.results || []),
37
+ });
38
+ }, [tales]);
39
+ return <Typography>Opowieści: {list.length}</Typography>;
40
+ }
41
+
42
+ /** Mesa (karczma): opowieści karczmiane + licznik bywalców. */
43
+ export function InnExample() {
44
+ const inn = useInn();
45
+ const [counts, setCounts] = useState({});
46
+ useEffect(() => {
47
+ inn.listTales({ page: 1, callback: () => {} });
48
+ inn.getMembersCount({ callback: (ret) => setCounts(ret || {}) });
49
+ }, [inn]);
50
+ return <Typography>Bywalców łącznie: {counts.countAll ?? 0}</Typography>;
51
+ }
52
+
53
+ /** Metropolia: lista planów warsztatu (przykład jednej z 17 akcji `useTown`). */
54
+ export function TownExample() {
55
+ const town = useTown();
56
+ const [plans, setPlans] = useState([]);
57
+ useEffect(() => {
58
+ town.workshopPlansList({ callback: (ret) => setPlans(ret?.results || ret || []) });
59
+ }, [town]);
60
+ return <Typography>Plany warsztatu: {plans.length}</Typography>;
61
+ }
62
+
63
+ /** Frakcje (gildie): publiczna lista. */
64
+ export function GuildExample() {
65
+ const guild = useGuild();
66
+ const [guilds, setGuilds] = useState([]);
67
+ useEffect(() => {
68
+ guild.list({ page: 1, callback: (ret) => setGuilds(ret?.results || ret || []) });
69
+ }, [guild]);
70
+ return <Typography>Frakcje: {guilds.length}</Typography>;
71
+ }
72
+
73
+ /** Dynastie (rody): lista rodów. */
74
+ export function FamilyExample() {
75
+ const family = useFamily();
76
+ const [families, setFamilies] = useState([]);
77
+ useEffect(() => {
78
+ family.list({ callback: (ret) => setFamilies(ret?.results || ret || []) });
79
+ }, [family]);
80
+ return <Typography>Rody: {families.length}</Typography>;
81
+ }
82
+
83
+ /** Kolonie (osady): moje osady. */
84
+ export function VillagesExample() {
85
+ const villages = useVillages();
86
+ const [list, setList] = useState([]);
87
+ useEffect(() => {
88
+ villages.list({ callback: (ret) => setList(ret?.results || ret || []) });
89
+ }, [villages]);
90
+ return <Typography>Kolonie: {list.length}</Typography>;
91
+ }
92
+
93
+ /** Zaopatrzenie (sklep): dostępne oferty premium. */
94
+ export function ShopExample() {
95
+ const shop = useShop();
96
+ const [offers, setOffers] = useState([]);
97
+ useEffect(() => {
98
+ shop.getOffers({ callback: (ret) => setOffers(ret?.results || ret || []) });
99
+ }, [shop]);
100
+ return <Typography>Oferty: {offers.length}</Typography>;
101
+ }
102
+
103
+ /** Notowania: ranking główny z filtrami. */
104
+ export function RankingExample() {
105
+ const ranking = useRanking();
106
+ const [rows, setRows] = useState([]);
107
+ useEffect(() => {
108
+ ranking.ranking({ page: 1, callback: (ret) => setRows(ret?.results || []) });
109
+ }, [ranking]);
110
+ return <Typography>Ranking — wpisów: {rows.length}</Typography>;
111
+ }
112
+
113
+ /** Profil (konto): informacje o koncie. */
114
+ export function AccountProfileExample() {
115
+ const profile = useAccountProfile();
116
+ const [info, setInfo] = useState(null);
117
+ useEffect(() => {
118
+ profile.getAccountInfo({ callback: (ret) => setInfo(ret || null) });
119
+ }, [profile]);
120
+ return <Typography>Konto: {info ? "wczytane" : "—"}</Typography>;
121
+ }
122
+
123
+ /** Konfiguracja: zmiana ustawienia (np. niewidoczność online). */
124
+ export function SettingsExample() {
125
+ const settings = useSettings();
126
+ const toggleInvisible = () =>
127
+ settings.setInvisibleOnline({ enabled: true, callback: () => {} });
128
+ return (
129
+ <Typography component="button" onClick={toggleInvisible}>
130
+ Ukryj online
131
+ </Typography>
132
+ );
133
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * PRZYKŁAD slotów (REFERENCJA — nieeksportowana). Sloty to ciężkie fragmenty z logiką/danymi,
3
+ * które renderuje HOST (mapa świata, statystyki zgłoszeń itp.). Skórka pobiera je gotowe przez
4
+ * `useSlots()` i tylko ROZMIESZCZA — nie ma dostępu do ich wnętrza ani do kodu gry.
5
+ *
6
+ * Zawsze degraduj łagodnie: gdy host nie dostarcza danego slotu, `slots[KLUCZ]` jest `undefined`.
7
+ */
8
+ import { Box, Typography } from "@mui/material";
9
+ import { useSlots, SLOT_KEYS } from "@hexah/skin-sdk";
10
+
11
+ /** Mapa Sektora (mapa świata + ruiny) wstawiona we własny kontener skórki. */
12
+ export function WorldMapExample() {
13
+ const slots = useSlots();
14
+ const WorldMap = slots[SLOT_KEYS.WORLD_MAP];
15
+ if (!WorldMap) {
16
+ return <Typography>Slot mapy niedostępny.</Typography>;
17
+ }
18
+ return (
19
+ <Box sx={{ position: "relative", height: "70vh" }}>
20
+ <WorldMap />
21
+ </Box>
22
+ );
23
+ }
24
+
25
+ /** Statystyki zgłoszeń (wykresy) — np. obok listy na ekranie zgłoszeń. */
26
+ export function IssueStatsExample() {
27
+ const slots = useSlots();
28
+ const IssueStats = slots[SLOT_KEYS.ISSUE_STATS];
29
+ return IssueStats ? <IssueStats /> : null;
30
+ }