mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat: add WeChat design system, login-flow skill, and fix API mode tool_calls bug - Add WeChat design system (design-systems/wechat/) with full brand spec including color palette, typography, and component rules for chat UI - Add login-flow skill (skills/login-flow/) for mobile authentication flows with P0 checklist, example HTML, and i18n registration across 3 locales - Fix DeepSeek V4 bug: API/BYOK mode (streamFormat=plain) models now receive a directive to emit only <artifact> HTML blocks and suppress tool_calls, since plain adapters proxy to external providers that cannot execute tools * fix: restore full server.ts and WeChat DESIGN.md from ad46d8cd commit Restore files that were corrupted in PR #1083 head branch. The WeChat DESIGN.md was reduced to a single line (filename only) and server.ts was reduced to ~1 line. Both are restored to their original ad46d8cd state with full content. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: restore full server.ts and WeChat DESIGN.md from ad46d8cd Restore files corrupted in PR #1083: - apps/daemon/src/server.ts: restored 7106-line file - design-systems/wechat/DESIGN.md: restored 301-line WeChat design spec - skills/login-flow/SKILL.md: restored from local working state - skills/login-flow/example.html: restored 351-line example HTML * fix: only suppress tool_calls when streamFormat='plain' explicitly, remove nonexistent assets/template.html 1. streamFormat check now requires explicit 'plain' value instead of defaulting to 'plain' when undefined. This prevents normal tool-using chat runs from incorrectly inheriting the API/BYOK tool_calls suppression rule. 2. login-flow SKILL.md: removed reference to assets/template.html since that file does not exist in the skill bundle and derivePreflight() would inject a hard instruction to read it before any other tool, causing pre-flight to fail. * fix: thread streamFormat to composeSystemPrompt in server.ts call Previously the composeSystemPrompt call at line ~4940 omitted streamFormat, causing the composer to default to 'plain' and suppress tool_calls even for tool-using chat runs. Now streamFormat is passed through from the adapter definition so the API mode rule only fires when streamFormat='plain' is explicitly set. * fix: WeChat category metadata, font-family, and login-flow example interactivity WeChat DESIGN.md: - Add Category: Social & Messaging metadata so it appears correctly in picker - Fix font-family declaration: remove invalid -webkit-font-family prefix, use standard font-family so downstream CSS generation works correctly skills/login-flow/example.html: - Add password toggle click handler so show/hide actually works - Change Apple icon fill from hardcoded #fff to currentColor so it is visible on light backgrounds * fix: mirror streamFormat suppression in contracts composer and add WeChat i18n 1. packages/contracts/src/prompts/system.ts: Add streamFormat parameter to ComposeInput and ComposeInput interface, mirroring the same suppression rule from daemon prompts/system.ts. When streamFormat='plain' is passed, a directive is appended telling models not to emit tool_calls and to only output <artifact> HTML blocks. 2. apps/web/src/i18n/content.{ts,fr,ru}.ts: Add WeChat design system entries: - Add 'wechat' to DE/FR/RU_DESIGN_SYSTEM_IDS_WITH_EN_FALLBACK arrays - Add 'wechat' summary to DE/FR/RU_DESIGN_SYSTEM_SUMMARIES - Add 'Social & Messaging' category to DE/FR/RU_DESIGN_SYSTEM_CATEGORIES (matching the Category: Social & Messaging metadata in WeChat DESIGN.md) * fix: thread streamFormat='plain' into web composeSystemPrompt for api mode * test: focus localized content coverage on missing resources --------- Co-authored-by: Open Design Contributor <z@open-design.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: mrcfps <mrc@powerformer.com>
362 lines
9.9 KiB
HTML
362 lines
9.9 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Nova · Login</title>
|
|
<style>
|
|
:root {
|
|
--bg: #F7F8FA;
|
|
--surface: #FFFFFF;
|
|
--ink: #1A1A2E;
|
|
--muted: #6B7280;
|
|
--border: #E5E7EB;
|
|
--brand: #4F46E5;
|
|
--brand-hover: #4338CA;
|
|
--error: #DC2626;
|
|
--success: #059669;
|
|
}
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: var(--bg);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px;
|
|
}
|
|
.phone {
|
|
width: 375px;
|
|
background: var(--surface);
|
|
border-radius: 40px;
|
|
overflow: hidden;
|
|
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.15);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.statusbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 24px 4px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--ink);
|
|
}
|
|
.statusbar .right {
|
|
display: flex;
|
|
gap: 6px;
|
|
align-items: center;
|
|
}
|
|
.status-icon {
|
|
width: 16px;
|
|
height: 10px;
|
|
background: var(--ink);
|
|
border-radius: 2px;
|
|
position: relative;
|
|
}
|
|
.status-icon::after {
|
|
content: '';
|
|
position: absolute;
|
|
right: -3px;
|
|
top: 2px;
|
|
width: 4px;
|
|
height: 4px;
|
|
border-radius: 50%;
|
|
background: var(--ink);
|
|
}
|
|
|
|
.content {
|
|
flex: 1;
|
|
padding: 32px 28px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
.logo-area {
|
|
text-align: center;
|
|
padding: 8px 0 4px;
|
|
}
|
|
.logo {
|
|
width: 56px;
|
|
height: 56px;
|
|
background: var(--brand);
|
|
border-radius: 14px;
|
|
margin: 0 auto 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.logo svg { width: 32px; height: 32px; fill: white; }
|
|
.app-name {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: var(--ink);
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.header { text-align: center; }
|
|
.header h1 {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--ink);
|
|
margin-bottom: 8px;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
.header p {
|
|
font-size: 14px;
|
|
color: var(--muted);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.form { display: flex; flex-direction: column; gap: 16px; }
|
|
|
|
.field { display: flex; flex-direction: column; gap: 6px; }
|
|
.field label {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--ink);
|
|
}
|
|
.input-wrap {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
.input-wrap input {
|
|
width: 100%;
|
|
height: 48px;
|
|
padding: 0 16px;
|
|
border: 1.5px solid var(--border);
|
|
border-radius: 12px;
|
|
font-size: 16px;
|
|
color: var(--ink);
|
|
background: var(--surface);
|
|
transition: border-color 0.2s;
|
|
}
|
|
.input-wrap input:focus {
|
|
outline: none;
|
|
border-color: var(--brand);
|
|
}
|
|
.input-wrap input::placeholder { color: var(--muted); }
|
|
.input-wrap .toggle {
|
|
position: absolute;
|
|
right: 14px;
|
|
width: 24px;
|
|
height: 24px;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
color: var(--muted);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.input-wrap .toggle:hover { color: var(--ink); }
|
|
|
|
.phone-input { display: flex; gap: 8px; }
|
|
.phone-input .country {
|
|
width: 90px;
|
|
height: 48px;
|
|
padding: 0 12px;
|
|
border: 1.5px solid var(--border);
|
|
border-radius: 12px;
|
|
font-size: 16px;
|
|
color: var(--ink);
|
|
background: var(--surface);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
cursor: pointer;
|
|
}
|
|
.phone-input .country .flag { font-size: 18px; }
|
|
.phone-input input { flex: 1; }
|
|
|
|
.row { display: flex; justify-content: flex-end; }
|
|
.forgot {
|
|
font-size: 13px;
|
|
color: var(--brand);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
.forgot:hover { text-decoration: underline; }
|
|
|
|
.btn-primary {
|
|
width: 100%;
|
|
height: 52px;
|
|
background: var(--brand);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 12px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
transition: background-color 0.2s;
|
|
}
|
|
.btn-primary:hover { background: var(--brand-hover); }
|
|
.btn-primary:disabled {
|
|
background: #A5B4FC;
|
|
cursor: not-allowed;
|
|
}
|
|
.btn-primary .spinner {
|
|
width: 18px;
|
|
height: 18px;
|
|
border: 2px solid rgba(255,255,255,0.3);
|
|
border-top-color: white;
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
.divider {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
color: var(--muted);
|
|
font-size: 13px;
|
|
}
|
|
.divider::before,
|
|
.divider::after {
|
|
content: '';
|
|
flex: 1;
|
|
height: 1px;
|
|
background: var(--border);
|
|
}
|
|
|
|
.social { display: flex; gap: 12px; }
|
|
.social-btn {
|
|
flex: 1;
|
|
height: 48px;
|
|
border: 1.5px solid var(--border);
|
|
border-radius: 12px;
|
|
background: var(--surface);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--ink);
|
|
transition: background-color 0.2s;
|
|
}
|
|
.social-btn:hover { background: var(--bg); }
|
|
.social-btn svg { width: 20px; height: 20px; }
|
|
|
|
.footer {
|
|
text-align: center;
|
|
font-size: 14px;
|
|
color: var(--muted);
|
|
padding-top: 8px;
|
|
}
|
|
.footer a {
|
|
color: var(--brand);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
.footer a:hover { text-decoration: underline; }
|
|
|
|
.error-msg {
|
|
font-size: 12px;
|
|
color: var(--error);
|
|
margin-top: 4px;
|
|
display: none;
|
|
}
|
|
.field.has-error input { border-color: var(--error); }
|
|
.field.has-error .error-msg { display: block; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="phone">
|
|
<div class="statusbar">
|
|
<span>9:41</span>
|
|
<div class="right">
|
|
<span>5G</span>
|
|
<span class="status-icon"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<div class="logo-area">
|
|
<div class="logo">
|
|
<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
|
|
</div>
|
|
<div class="app-name">Nova</div>
|
|
</div>
|
|
|
|
<div class="header">
|
|
<h1>Welcome back</h1>
|
|
<p>Sign in to continue to your account</p>
|
|
</div>
|
|
|
|
<form class="form" onsubmit="return false;">
|
|
<div class="field">
|
|
<label for="phone">Phone number</label>
|
|
<div class="phone-input">
|
|
<div class="country">
|
|
<span class="flag">+86</span>
|
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M3 5l3 3 3-3"/></svg>
|
|
</div>
|
|
<input type="tel" id="phone" placeholder="123 4567 8900" />
|
|
</div>
|
|
<span class="error-msg">Please enter a valid phone number</span>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="password">Password</label>
|
|
<div class="input-wrap">
|
|
<input type="password" id="password" placeholder="Enter your password" />
|
|
<button type="button" class="toggle" aria-label="Show password">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
|
<circle cx="12" cy="12" r="3"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<span class="error-msg">Password is required</span>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<a href="#" class="forgot">Forgot password?</a>
|
|
</div>
|
|
|
|
<button type="submit" class="btn-primary">
|
|
<span class="btn-text">Sign in</span>
|
|
</button>
|
|
|
|
<div class="divider">or continue with</div>
|
|
|
|
<div class="social">
|
|
<button type="button" class="social-btn">
|
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.025-1.632 2.815-.181.79-.065 1.62.116 2.43.143.4.367.78.666 1.106.315.345.69.647 1.114.918.927.568 2.013.858 3.183.955 1.18.098 2.063-.018 2.94-.123 1.05-.126 1.92-.78 2.528-1.54 1.12-1.38 1.62-3.24 1.337-4.6z"/></svg>
|
|
Apple
|
|
</button>
|
|
<button type="button" class="social-btn">
|
|
<svg viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>
|
|
Google
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="footer">
|
|
Don't have an account? <a href="#">Sign up</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
<script>
|
|
const toggleBtn = document.querySelector('.toggle');
|
|
const passwordInput = document.getElementById('password');
|
|
if (toggleBtn && passwordInput) {
|
|
toggleBtn.addEventListener('click', function () {
|
|
const isPassword = passwordInput.type === 'password';
|
|
passwordInput.type = isPassword ? 'text' : 'password';
|
|
toggleBtn.setAttribute('aria-label', isPassword ? 'Hide password' : 'Show password');
|
|
});
|
|
}
|
|
</script>
|
|
</html>
|