mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
AC0 задачи: пока референс лежит вложением в issue, ревью сравнивает продукт с макетом «на глаз», и ровно это дало выпущенный в 1.77.0-beta.2 ложный паритет — карточки снаружи, старая разметка внутри. Содержимое архива `houseplan-settings-design-2026-09-18.zip` (SHA-256 06f07fc212947ee9da1a39aaab2d14cbd41154cc9effe39bc48e1c2e1517255b, сверен побайтово с вложением #591) разложено по образцу #505: - `SPEC.md`, `IMPLEMENTATION-GUIDE.md`, `OPEN-POINTS.md`, карта полей Space — с этого коммита часть ТЗ: расхождение называется ссылкой на пункт документа; - `reference/` — свёрстанный прототип четырёх форм; - `screenshots/` — четыре формы целиком, 560 px, 2x; - `README.md` — происхождение, отпечаток, границы использования; - `ACCEPTANCE.md` — место для парных кадров «референс / продукт» по AC1. Автор — сотрудник владельца (подтверждено 20.09 в #600), внешний грант не нужен. Windows-лаунчеры `START.cmd` и `START.ps1` не переносились: `node serve.mjs` делает то же самое. `room-fill.test.mjs` перенесён как документ намерения, CI его не запускает — под `docs/**` не попадает ни один тестовый glob. В бандл отсюда не попадает ничего; `repo-hygiene` и полный набор зелёные (2793 pass). Issue: #600 User-Visible: no
23 lines
1.6 KiB
JavaScript
23 lines
1.6 KiB
JavaScript
import http from 'node:http';
|
|
import {readFile, stat, realpath} from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import {fileURLToPath} from 'node:url';
|
|
const root = await realpath(path.dirname(fileURLToPath(import.meta.url)));
|
|
const port = Number(process.env.HOUSEPLAN_SETTINGS_PORT || 8135);
|
|
const types = {'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'text/javascript; charset=utf-8','.svg':'image/svg+xml','.png':'image/png','.md':'text/plain; charset=utf-8','.json':'application/json; charset=utf-8'};
|
|
const server=http.createServer(async(req,res)=>{
|
|
try {
|
|
if(!['GET','HEAD'].includes(req.method)){res.writeHead(405);res.end();return;}
|
|
const pathname=decodeURIComponent(new URL(req.url,'http://127.0.0.1').pathname);
|
|
const requested=path.resolve(root,'.'+(pathname==='/'?'/index.html':pathname));
|
|
const file=await realpath(requested);
|
|
const relative=path.relative(root,file);
|
|
if(relative.startsWith('..')||path.isAbsolute(relative)||!(await stat(file)).isFile()){res.writeHead(403);res.end();return;}
|
|
const data=await readFile(file);
|
|
res.writeHead(200,{'Content-Type':types[path.extname(file)]||'application/octet-stream','Cache-Control':'no-store','X-Content-Type-Options':'nosniff'});
|
|
res.end(req.method==='HEAD'?undefined:data);
|
|
} catch {res.writeHead(404);res.end('Not found');}
|
|
});
|
|
server.on('error',error=>{console.error(error.code==='EADDRINUSE'?`Port ${port} is already in use.`:error.message);process.exitCode=1;});
|
|
server.listen(port,'127.0.0.1',()=>console.log(`Space settings design preview: http://127.0.0.1:${port}`));
|