feat(ios): P1-A — server touch-points (lastOutputAt, APNs sender+token endpoint) + APIClient P1 contract
T-iOS-37: LiveSessionInfo.lastOutputAt additive-optional field + manager.list() mapping (+4 tests; web consumers unaffected) T-iOS-20: src/push/apns.ts — env-gated (all-or-disabled, no crash, zero key-material logging), hand-rolled ES256 JWT (node:crypto ieee-p1363, zero new deps), NEEDS-INPUT/DONE payloads with structural minimization, token store per subscription-store conventions, combineNotifyServices parallel to web-push, POST/DELETE /push/apns-token per frozen wire shape (G-guard, 5/min/IP, 8kb); 65 tests incl. dual-channel e2e vs local fake APNs T-iOS-38: APIClient builders — apns-token (client-side hex mirror, pre-network reject), projects/detail (lossy decode, single-point percent-encoding), prefs (unknown-key byte-exact round-trip preservation), public four-tier HostNetworkTier Coordination: webterminal:// CFBundleURLTypes pre-registered in project.yml for T-iOS-22 Verified: root 1470 tests + tsc clean; APIClient 76 tests, 95.26% coverage; wire-shape cross-check zero mismatches
This commit is contained in:
@@ -1049,3 +1049,66 @@ describe('list — telemetry field', () => {
|
||||
expect(entry?.telemetry).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ── list() carries lastOutputAt (T-iOS-37 unread watermark) ───────────────────
|
||||
// The runtime record (Session.lastOutputAt, refreshed on every pty.onData — M3)
|
||||
// is serialized into LiveSessionInfo so clients can compute an unread dot
|
||||
// (lastOutputAt > local last-seen). Additive OPTIONAL field: existing consumers
|
||||
// (preview grid, projects panel) are unaffected.
|
||||
describe('list — lastOutputAt field (T-iOS-37)', () => {
|
||||
it('includes lastOutputAt matching the session runtime record after output flows', () => {
|
||||
const mgr = createSessionManager(CFG);
|
||||
nextPty = createMockPty();
|
||||
const s = mgr.handleAttach(createMockWs(), null, DIMS, 1_000);
|
||||
|
||||
// Output flows → session.ts pty.onData refreshes the runtime record with
|
||||
// Date.now(); list() must serialize exactly that value.
|
||||
const before = Date.now();
|
||||
(s.pty as MockIPty).emitData('hello from claude');
|
||||
|
||||
const entry = mgr.list().find((e) => e.id === s.meta.id);
|
||||
expect(s.lastOutputAt).toBeGreaterThanOrEqual(before);
|
||||
expect(entry?.lastOutputAt).toBe(s.lastOutputAt);
|
||||
});
|
||||
|
||||
it('serializes a pinned runtime value verbatim (epoch ms, no transformation)', () => {
|
||||
const mgr = createSessionManager(CFG);
|
||||
nextPty = createMockPty();
|
||||
const s = mgr.handleAttach(createMockWs(), null, DIMS, 1_000);
|
||||
|
||||
// Pin the runtime record to a known cursor (same style as the M3 reap tests).
|
||||
s.lastOutputAt = 123_456;
|
||||
|
||||
const entry = mgr.list().find((e) => e.id === s.meta.id);
|
||||
expect(entry?.lastOutputAt).toBe(123_456);
|
||||
});
|
||||
|
||||
it('equals spawn time for a fresh session before any output (createSession inits lastOutputAt = now)', () => {
|
||||
const mgr = createSessionManager(CFG);
|
||||
nextPty = createMockPty();
|
||||
const s = mgr.handleAttach(createMockWs(), null, DIMS, 1_000);
|
||||
|
||||
// createSession initializes lastOutputAt: now (src/session/session.ts) —
|
||||
// a fresh session's watermark IS its spawn time, never absent/zero.
|
||||
const entry = mgr.list().find((e) => e.id === s.meta.id);
|
||||
expect(entry?.lastOutputAt).toBe(1_000);
|
||||
});
|
||||
|
||||
it('keeps the existing LiveSessionInfo shape intact (additive optional field)', () => {
|
||||
const mgr = createSessionManager(CFG);
|
||||
nextPty = createMockPty();
|
||||
const s = mgr.handleAttach(createMockWs(), null, DIMS, 1_000);
|
||||
|
||||
const entry = mgr.list().find((e) => e.id === s.meta.id);
|
||||
expect(entry).toMatchObject({
|
||||
id: s.meta.id,
|
||||
createdAt: 1_000,
|
||||
clientCount: 1,
|
||||
status: 'unknown',
|
||||
exited: false,
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
telemetry: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user