sync: update from private repo (f6d625db)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-26 03:35:45 +00:00
parent 29ccaf1e92
commit b857c33ef6
371 changed files with 31312 additions and 8172 deletions
@@ -0,0 +1,5 @@
{
"title": "ダッシュボード",
"description": "output/ を集計して表示",
"entry": "index.html"
}
@@ -0,0 +1,15 @@
{
"app": "dashboard",
"seed_files": [
{ "path": "output/a.txt", "content": "x\ny" },
{ "path": "output/b.txt", "content": "z" }
],
"steps": [
{ "type": "click", "selector": "[data-testid=\"refresh\"]" },
{ "type": "getText", "selector": "[data-testid=\"summary\"]" }
],
"expect": [
{ "kind": "text", "target": "[data-testid=\"summary\"]", "contains": "ファイル数" },
{ "kind": "text", "target": "[data-testid=\"status\"]", "contains": "更新OK" }
]
}
@@ -0,0 +1,72 @@
<!doctype html><html lang="ja"><head><meta charset="utf-8" />
<style>
body{font:14px system-ui;margin:0;padding:16px}
.row{display:flex;gap:8px;align-items:center;margin-bottom:12px}
#summary{font-size:16px;font-weight:bold;margin-bottom:16px;min-height:24px}
.bar-wrap{margin-bottom:16px}
.bar-label{font-size:12px;margin-bottom:2px;color:#444}
svg.bar{display:block;width:100%;max-width:400px;height:20px;border-radius:4px;overflow:hidden;background:#eee}
rect.bar-fill{fill:#2563eb}
span[data-testid="status"]{font-size:12px;color:#555}
button{padding:6px 16px;border-radius:4px;border:none;background:#2563eb;color:#fff;cursor:pointer}
button:hover{background:#1d4ed8}
</style></head>
<body>
<div class="row">
<button id="refreshBtn" data-testid="refresh">更新</button>
<span id="status" data-testid="status"></span>
</div>
<div id="summary" data-testid="summary"></div>
<div id="bars"></div>
<script>
let seq=0;const pending=new Map();
function call(req){return new Promise((res,rej)=>{const id=++seq;pending.set(id,{res,rej});
window.parent.postMessage({...req,id},'*');});}
window.addEventListener('message',e=>{const r=e.data;const p=pending.get(r&&r.id);if(!p)return;
pending.delete(r.id);r.ok?p.res(r.data):p.rej(new Error(r.error));});
const $=id=>document.getElementById(id);
const TEXT_EXTS=['.txt','.md','.csv','.json','.jsonl','.log','.yaml','.yml'];
function isText(name){return TEXT_EXTS.some(e=>name.endsWith(e));}
async function refresh(){
$('status').textContent='集計中…';
$('summary').textContent='';
$('bars').innerHTML='';
try{
const d=await call({type:'listFiles',dir:'output'});
const entries=d.entries||[];
const count=entries.length;
const lineCounts=[];
for(const name of entries){
if(!isText(name)){lineCounts.push({name,lines:null});continue;}
try{
const fd=await call({type:'readFile',path:'output/'+name});
const lines=(fd.content||'').split('\n').filter(l=>l.length>0).length;
lineCounts.push({name,lines});
}catch(_){lineCounts.push({name,lines:null});}
}
$('summary').textContent='ファイル数: '+count;
const withLines=lineCounts.filter(x=>x.lines!==null);
const maxLines=Math.max(...withLines.map(x=>x.lines),1);
withLines.forEach(({name,lines})=>{
const wrap=document.createElement('div');wrap.className='bar-wrap';
const lbl=document.createElement('div');lbl.className='bar-label';
lbl.textContent=name+' ('+lines+'行)';
const pct=Math.round((lines/maxLines)*100);
const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('class','bar');svg.setAttribute('viewBox','0 0 100 20');
svg.setAttribute('preserveAspectRatio','none');
const rect=document.createElementNS('http://www.w3.org/2000/svg','rect');
rect.setAttribute('class','bar-fill');rect.setAttribute('x','0');rect.setAttribute('y','0');
rect.setAttribute('width',String(pct));rect.setAttribute('height','20');
svg.appendChild(rect);wrap.appendChild(lbl);wrap.appendChild(svg);
$('bars').appendChild(wrap);
});
$('status').textContent='更新OK';
}catch(err){$('status').textContent='エラー:'+err.message;}
}
$('refreshBtn').onclick=refresh;
refresh();
</script></body></html>
@@ -0,0 +1,5 @@
{
"title": "データビューア",
"description": "output/ のファイルを一覧・閲覧",
"entry": "index.html"
}
@@ -0,0 +1,14 @@
{
"app": "data-viewer",
"seed_files": [
{ "path": "output/data.csv", "content": "name,age\nalice,30\nbob,25" }
],
"steps": [
{ "type": "click", "selector": "[data-testid=\"list\"]" },
{ "type": "getText", "selector": "[data-testid=\"files\"]" }
],
"expect": [
{ "kind": "text", "target": "[data-testid=\"files\"]", "contains": "data.csv" },
{ "kind": "text", "target": "[data-testid=\"status\"]", "contains": "取得OK" }
]
}
@@ -0,0 +1,81 @@
<!doctype html><html lang="ja"><head><meta charset="utf-8" />
<style>
body{font:14px system-ui;margin:0;padding:16px}
.row{display:flex;gap:8px;align-items:center;margin-bottom:8px}
ul{list-style:none;padding:0;margin:0}
li[data-testid="file-item"]{cursor:pointer;padding:4px 8px;border-radius:4px;background:#f0f0f0;margin-bottom:4px}
li[data-testid="file-item"]:hover{background:#dce8ff}
pre[data-testid="preview"]{background:#f9f9f9;border:1px solid #ddd;padding:12px;overflow:auto;max-height:50vh;white-space:pre-wrap}
table{border-collapse:collapse;width:100%}
table th,table td{border:1px solid #ccc;padding:4px 8px;text-align:left}
table th{background:#eee}
</style></head>
<body>
<div class="row">
<input id="dir" data-testid="dir" value="output" style="flex:1" />
<button id="listBtn" data-testid="list">一覧取得</button>
<span id="status" data-testid="status"></span>
</div>
<ul id="files" data-testid="files"></ul>
<div id="preview-wrap"></div>
<script>
let seq=0;const pending=new Map();
function call(req){return new Promise((res,rej)=>{const id=++seq;pending.set(id,{res,rej});
window.parent.postMessage({...req,id},'*');});}
window.addEventListener('message',e=>{const r=e.data;const p=pending.get(r&&r.id);if(!p)return;
pending.delete(r.id);r.ok?p.res(r.data):p.rej(new Error(r.error));});
const $=id=>document.getElementById(id);
function renderCsv(text){
const rows=text.trim().split('\n').map(r=>r.split(','));
const table=document.createElement('table');
rows.forEach((cols,i)=>{const tr=document.createElement('tr');
cols.forEach(c=>{const cell=document.createElement(i===0?'th':'td');cell.textContent=c;tr.appendChild(cell);});
table.appendChild(tr);});
return table;
}
async function listDir(){
const dir=$('dir').value.trim()||'output';
$('status').textContent='取得中…';
$('files').innerHTML='';
$('preview-wrap').innerHTML='';
try{
const d=await call({type:'listFiles',dir});
const entries=d.entries||[];
if(entries.length===0){$('files').innerHTML='<li>(ファイルなし)</li>';}
entries.forEach(name=>{
const li=document.createElement('li');
li.setAttribute('data-testid','file-item');
li.textContent=name;
li.onclick=()=>openFile(dir,name);
$('files').appendChild(li);
});
$('status').textContent='取得OK';
}catch(err){$('status').textContent='エラー:'+err.message;}
}
async function openFile(dir,name){
$('status').textContent='読込中…';
$('preview-wrap').innerHTML='';
try{
const path=dir.replace(/\/$/,'')+'/'+name;
const d=await call({type:'readFile',path});
const content=d.content||'';
if(name.endsWith('.csv')){
const wrap=document.createElement('div');
wrap.setAttribute('data-testid','preview');
wrap.appendChild(renderCsv(content));
$('preview-wrap').appendChild(wrap);
}else{
const pre=document.createElement('pre');
pre.setAttribute('data-testid','preview');
pre.textContent=content;
$('preview-wrap').appendChild(pre);
}
$('status').textContent='読込OK';
}catch(err){$('status').textContent='読込失敗:'+err.message;}
}
$('listBtn').onclick=listDir;
</script></body></html>
@@ -0,0 +1,5 @@
{
"title": "フォーム入力",
"description": "入力を output/ に構造化保存",
"entry": "index.html"
}
@@ -0,0 +1,14 @@
{
"app": "form-input",
"seed_files": [],
"steps": [
{ "type": "fill", "selector": "[data-testid=\"f-name\"]", "value": "alice" },
{ "type": "fill", "selector": "[data-testid=\"f-note\"]", "value": "hello-e2e" },
{ "type": "click", "selector": "[data-testid=\"submit\"]" },
{ "type": "getText", "selector": "[data-testid=\"status\"]" }
],
"expect": [
{ "kind": "text", "target": "[data-testid=\"status\"]", "contains": "保存" },
{ "kind": "file", "target": "output/submissions.jsonl", "contains": "hello-e2e" }
]
}
@@ -0,0 +1,49 @@
<!doctype html><html lang="ja"><head><meta charset="utf-8" />
<style>
body{font:14px system-ui;margin:0;padding:16px}
.row{display:flex;gap:8px;align-items:center;margin-bottom:8px}
input[type="text"],textarea{border:1px solid #ccc;border-radius:4px;padding:6px 8px;width:100%;box-sizing:border-box}
label{display:block;font-weight:bold;margin-bottom:4px;margin-top:12px}
button{padding:6px 16px;border-radius:4px;border:none;background:#2563eb;color:#fff;cursor:pointer}
button:hover{background:#1d4ed8}
span[data-testid="status"]{font-size:12px;color:#555}
</style></head>
<body>
<label for="f-name">名前</label>
<input type="text" id="f-name" data-testid="f-name" placeholder="名前を入力" />
<label for="f-note">メモ</label>
<input type="text" id="f-note" data-testid="f-note" placeholder="メモを入力" />
<div class="row" style="margin-top:16px">
<button id="submitBtn" data-testid="submit">保存</button>
<span id="status" data-testid="status"></span>
</div>
<script>
let seq=0;const pending=new Map();
function call(req){return new Promise((res,rej)=>{const id=++seq;pending.set(id,{res,rej});
window.parent.postMessage({...req,id},'*');});}
window.addEventListener('message',e=>{const r=e.data;const p=pending.get(r&&r.id);if(!p)return;
pending.delete(r.id);r.ok?p.res(r.data):p.rej(new Error(r.error));});
const $=id=>document.getElementById(id);
const DEST='output/submissions.jsonl';
async function submit(){
const name=$('f-name').value.trim();
const note=$('f-note').value.trim();
if(!name&&!note){$('status').textContent='入力してください';return;}
$('status').textContent='保存中…';
try{
let existing='';
try{const d=await call({type:'readFile',path:DEST});existing=d.content||'';}catch(_){}
const line=JSON.stringify({name,note,ts:new Date().toISOString()});
const updated=(existing.endsWith('\n')||existing==='')
?existing+line+'\n'
:existing+'\n'+line+'\n';
await call({type:'writeFile',path:DEST,content:updated});
$('f-name').value='';$('f-note').value='';
$('status').textContent='保存しました';
}catch(err){$('status').textContent='保存失敗:'+err.message;}
}
$('submitBtn').onclick=submit;
</script></body></html>
@@ -0,0 +1,5 @@
{
"title": "ノートエディタ",
"description": "output/ のテキストを編集して保存",
"entry": "index.html"
}
@@ -0,0 +1,15 @@
{
"app": "note-editor",
"seed_files": [{ "path": "output/note.md", "content": "seeded-content" }],
"steps": [
{ "type": "click", "selector": "[data-testid=\"load\"]" },
{ "type": "getText", "selector": "[data-testid=\"status\"]" },
{ "type": "fill", "selector": "[data-testid=\"body\"]", "value": "edited-by-e2e" },
{ "type": "click", "selector": "[data-testid=\"save\"]" },
{ "type": "getText", "selector": "[data-testid=\"status\"]" }
],
"expect": [
{ "kind": "text", "target": "[data-testid=\"status\"]", "contains": "保存OK" },
{ "kind": "file", "target": "output/note.md", "contains": "edited-by-e2e" }
]
}
@@ -0,0 +1,25 @@
<!doctype html><html lang="ja"><head><meta charset="utf-8" />
<style>body{font:14px system-ui;margin:0;padding:16px}textarea{width:100%;height:60vh}
.row{display:flex;gap:8px;align-items:center;margin-bottom:8px}</style></head>
<body>
<div class="row">
<input id="path" data-testid="path" value="output/note.md" style="flex:1" />
<button id="load" data-testid="load">読み込み</button>
<button id="save" data-testid="save">保存</button>
<span id="status" data-testid="status"></span>
</div>
<textarea id="body" data-testid="body"></textarea>
<script>
let seq=0;const pending=new Map();
function call(req){return new Promise((res,rej)=>{const id=++seq;pending.set(id,{res,rej});
window.parent.postMessage({...req,id},'*');});}
window.addEventListener('message',e=>{const r=e.data;const p=pending.get(r&&r.id);if(!p)return;
pending.delete(r.id);r.ok?p.res(r.data):p.rej(new Error(r.error));});
const $=id=>document.getElementById(id);
async function load(){try{const d=await call({type:'readFile',path:$('path').value});
$('body').value=d.content;$('status').textContent='読込OK';}catch(err){$('status').textContent='読込失敗:'+err.message;}}
async function save(){try{await call({type:'writeFile',path:$('path').value,content:$('body').value});
$('status').textContent='保存OK';}catch(err){$('status').textContent='保存失敗:'+err.message;}}
$('load').onclick=load;$('save').onclick=save;
load();
</script></body></html>