chore(release): clean up repo for v0.0.1 release

Excluded from release bundle:
- CONTEXT.md, CHANGELOG.md (agent/project working notes)
- client-app/ (React Native messenger — tracked separately)
- contracts/hello_go/ (unused standalone example)

Kept contracts/counter/ and contracts/name_registry/ as vm-test fixtures
(referenced by vm/vm_test.go; NOT production contracts).

Docs refactor:
- docs/README.md — new top-level index with cross-references
- docs/quickstart.md — rewrite around single-node as primary path
- docs/node/README.md — full rewrite, all CLI flags, schema table
- docs/api/README.md — add /api/well-known-version, /api/update-check
- docs/contracts/README.md — split native (Go) vs WASM (user-deployable)
- docs/update-system.md — new, full 5-layer update system design
- README.md — link into docs/, drop CHANGELOG/client-app references

Build-time version system (inherited from earlier commits this branch):
- node --version / client --version with ldflags-injected metadata
- /api/well-known-version with {build, protocol_version, features[]}
- Peer-version gossip on dchain/version/v1
- /api/update-check against Gitea release API
- deploy/single/update.sh with semver guard + 15-min systemd jitter
This commit is contained in:
vsecoder
2026-04-17 14:37:00 +03:00
parent 7e7393e4f8
commit 546d2c503f
55 changed files with 702 additions and 17381 deletions

View File

@@ -1,301 +0,0 @@
/**
* Import Existing Key screen.
* Two methods:
* 1. Paste JSON directly into a text field
* 2. Pick key.json file via document picker
*/
import React, { useState } from 'react';
import {
View, Text, ScrollView, TextInput,
TouchableOpacity, Alert, Pressable,
} from 'react-native';
import { router } from 'expo-router';
import * as DocumentPicker from 'expo-document-picker';
import * as Clipboard from 'expo-clipboard';
import { saveKeyFile } from '@/lib/storage';
import { useStore } from '@/lib/store';
import { Button } from '@/components/ui/Button';
import type { KeyFile } from '@/lib/types';
type Tab = 'paste' | 'file';
const REQUIRED_FIELDS: (keyof KeyFile)[] = ['pub_key', 'priv_key', 'x25519_pub', 'x25519_priv'];
function validateKeyFile(raw: string): KeyFile {
let parsed: any;
try {
parsed = JSON.parse(raw.trim());
} catch {
throw new Error('Invalid JSON — check that you copied the full key file contents.');
}
for (const field of REQUIRED_FIELDS) {
if (!parsed[field] || typeof parsed[field] !== 'string') {
throw new Error(`Missing or invalid field: "${field}"`);
}
if (!/^[0-9a-f]+$/i.test(parsed[field])) {
throw new Error(`Field "${field}" must be a hex string.`);
}
}
return parsed as KeyFile;
}
export default function ImportKeyScreen() {
const setKeyFile = useStore(s => s.setKeyFile);
const [tab, setTab] = useState<Tab>('paste');
const [jsonText, setJsonText] = useState('');
const [fileName, setFileName] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// ── Shared: save validated key and navigate ──────────────────────────────
async function applyKey(kf: KeyFile) {
setLoading(true);
setError(null);
try {
await saveKeyFile(kf);
setKeyFile(kf);
router.replace('/(app)/chats');
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
}
// ── Method 1: paste JSON ─────────────────────────────────────────────────
async function handlePasteImport() {
setError(null);
const text = jsonText.trim();
if (!text) {
// Try reading clipboard if field is empty
const clip = await Clipboard.getStringAsync();
if (clip) setJsonText(clip);
return;
}
try {
const kf = validateKeyFile(text);
await applyKey(kf);
} catch (e: any) {
setError(e.message);
}
}
// ── Method 2: pick file ──────────────────────────────────────────────────
async function pickFile() {
setError(null);
try {
const result = await DocumentPicker.getDocumentAsync({
type: ['application/json', 'text/plain', '*/*'],
copyToCacheDirectory: true,
});
if (result.canceled) return;
const asset = result.assets[0];
setFileName(asset.name);
// Use fetch() — readAsStringAsync is deprecated in newer expo-file-system
const response = await fetch(asset.uri);
const raw = await response.text();
const kf = validateKeyFile(raw);
await applyKey(kf);
} catch (e: any) {
setError(e.message);
}
}
const tabStyle = (t: Tab) => ({
flex: 1 as const,
paddingVertical: 10,
alignItems: 'center' as const,
borderBottomWidth: 2,
borderBottomColor: tab === t ? '#2563eb' : 'transparent',
});
const tabTextStyle = (t: Tab) => ({
fontSize: 14,
fontWeight: '600' as const,
color: tab === t ? '#fff' : '#8b949e',
});
return (
<ScrollView
style={{ flex: 1, backgroundColor: '#0d1117' }}
contentContainerStyle={{ paddingHorizontal: 20, paddingTop: 60, paddingBottom: 40 }}
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
>
{/* Back */}
<Pressable onPress={() => router.back()} style={{ marginBottom: 24, alignSelf: 'flex-start' }}>
<Text style={{ color: '#2563eb', fontSize: 15 }}> Back</Text>
</Pressable>
<Text style={{ color: '#fff', fontSize: 28, fontWeight: '700', marginBottom: 6 }}>
Import Key
</Text>
<Text style={{ color: '#8b949e', fontSize: 15, lineHeight: 22, marginBottom: 24 }}>
Restore your account from an existing{' '}
<Text style={{ color: '#fff', fontFamily: 'monospace' }}>key.json</Text>.
</Text>
{/* Tabs */}
<View style={{
flexDirection: 'row',
borderBottomWidth: 1, borderBottomColor: '#30363d',
marginBottom: 24,
}}>
<TouchableOpacity style={tabStyle('paste')} onPress={() => setTab('paste')}>
<Text style={tabTextStyle('paste')}>📋 Paste JSON</Text>
</TouchableOpacity>
<TouchableOpacity style={tabStyle('file')} onPress={() => setTab('file')}>
<Text style={tabTextStyle('file')}>📁 Open File</Text>
</TouchableOpacity>
</View>
{/* ── Paste tab ── */}
{tab === 'paste' && (
<View style={{ gap: 12 }}>
<Text style={{ color: '#8b949e', fontSize: 12, fontWeight: '600',
textTransform: 'uppercase', letterSpacing: 1 }}>
Key JSON
</Text>
<View style={{
backgroundColor: '#161b22',
borderWidth: 1,
borderColor: error ? '#f85149' : jsonText ? '#2563eb' : '#30363d',
borderRadius: 12,
padding: 12,
}}>
<TextInput
value={jsonText}
onChangeText={t => { setJsonText(t); setError(null); }}
placeholder={'{\n "pub_key": "...",\n "priv_key": "...",\n "x25519_pub": "...",\n "x25519_priv": "..."\n}'}
placeholderTextColor="#8b949e"
multiline
numberOfLines={8}
autoCapitalize="none"
autoCorrect={false}
style={{
color: '#fff',
fontFamily: 'monospace',
fontSize: 12,
lineHeight: 18,
minHeight: 160,
textAlignVertical: 'top',
}}
/>
</View>
{/* Paste from clipboard shortcut */}
{!jsonText && (
<TouchableOpacity
onPress={async () => {
const clip = await Clipboard.getStringAsync();
if (clip) { setJsonText(clip); setError(null); }
else Alert.alert('Clipboard empty', 'Copy your key JSON first.');
}}
style={{
flexDirection: 'row', alignItems: 'center', gap: 8,
padding: 12, backgroundColor: '#161b22',
borderWidth: 1, borderColor: '#30363d', borderRadius: 12,
}}
>
<Text style={{ fontSize: 18 }}>📋</Text>
<Text style={{ color: '#8b949e', fontSize: 14 }}>Paste from clipboard</Text>
</TouchableOpacity>
)}
{error && (
<View style={{
backgroundColor: 'rgba(248,81,73,0.1)',
borderWidth: 1, borderColor: 'rgba(248,81,73,0.3)',
borderRadius: 10, padding: 12,
}}>
<Text style={{ color: '#f85149', fontSize: 13, lineHeight: 18 }}> {error}</Text>
</View>
)}
<Button
size="lg"
loading={loading}
disabled={!jsonText.trim()}
onPress={handlePasteImport}
>
Import Key
</Button>
</View>
)}
{/* ── File tab ── */}
{tab === 'file' && (
<View style={{ gap: 12 }}>
<TouchableOpacity
onPress={pickFile}
style={{
backgroundColor: '#161b22',
borderWidth: 2, borderColor: '#30363d',
borderRadius: 16, borderStyle: 'dashed',
padding: 32, alignItems: 'center', gap: 12,
}}
>
<Text style={{ fontSize: 40 }}>📂</Text>
<Text style={{ color: '#fff', fontSize: 16, fontWeight: '600' }}>
{fileName ?? 'Choose key.json'}
</Text>
<Text style={{ color: '#8b949e', fontSize: 13, textAlign: 'center' }}>
Tap to browse files
</Text>
</TouchableOpacity>
{fileName && (
<View style={{
flexDirection: 'row', alignItems: 'center', gap: 10,
backgroundColor: 'rgba(63,185,80,0.1)',
borderWidth: 1, borderColor: 'rgba(63,185,80,0.3)',
borderRadius: 10, padding: 12,
}}>
<Text style={{ fontSize: 18 }}>📄</Text>
<Text style={{ color: '#3fb950', fontSize: 13, flex: 1 }} numberOfLines={1}>
{fileName}
</Text>
</View>
)}
{error && (
<View style={{
backgroundColor: 'rgba(248,81,73,0.1)',
borderWidth: 1, borderColor: 'rgba(248,81,73,0.3)',
borderRadius: 10, padding: 12,
}}>
<Text style={{ color: '#f85149', fontSize: 13, lineHeight: 18 }}> {error}</Text>
</View>
)}
{loading && (
<Text style={{ color: '#8b949e', textAlign: 'center', fontSize: 14 }}>
Validating key
</Text>
)}
</View>
)}
{/* Format hint */}
<View style={{
marginTop: 28, padding: 14,
backgroundColor: '#161b22',
borderWidth: 1, borderColor: '#30363d', borderRadius: 12,
}}>
<Text style={{ color: '#8b949e', fontSize: 12, fontWeight: '600',
marginBottom: 8, textTransform: 'uppercase', letterSpacing: 1 }}>
Expected format
</Text>
<Text style={{ color: '#8b949e', fontFamily: 'monospace', fontSize: 11, lineHeight: 17 }}>
{`{\n "pub_key": "<64 hex chars>",\n "priv_key": "<128 hex chars>",\n "x25519_pub": "<64 hex chars>",\n "x25519_priv": "<64 hex chars>"\n}`}
</Text>
</View>
</ScrollView>
);
}