@bongos/core 1.21.112 → 1.21.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +616 -186
- package/.claude/skills/ask-for-help/SKILL.md +1 -1
- package/.claude/skills/bongos-feedback/SKILL.md +1 -1
- package/.claude/skills/builder-sequence/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -0
- package/GOVERNMENT.md +128 -11
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +28 -0
- package/clients/bongos-client/index.cjs +28 -0
- package/clients/bongos-client/index.d.ts +45 -2
- package/clients/bongos-client/index.mjs +28 -0
- package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
- package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
- package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
- package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
- package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
- package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
- package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
- package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
- package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
- package/docs/adr/README.md +10 -1
- package/docs/api/openapi.json +989 -87
- package/docs/api-reference.md +18 -4
- package/docs/copy-inventory.md +343 -262
- package/docs/copy-registry.json +1223 -426
- package/docs/file-map.md +2 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/packs/engineer.md +2 -1
- package/docs/page-inventory.json +91 -6
- package/docs/page-readings.json +5017 -4458
- package/modules/builder-settings/law-locks.js +202 -0
- package/modules/builder-settings/routes/settings.js +66 -6
- package/modules/builder-settings/settings.js +7 -0
- package/modules/discord/ship-broadcast.js +22 -13
- package/modules/government/asks-db.js +54 -0
- package/modules/government/asks.js +269 -0
- package/modules/government/board-idea-reads.js +68 -0
- package/modules/government/board.js +21 -57
- package/modules/government/build-rights.js +148 -0
- package/modules/government/buildouts.js +160 -0
- package/modules/government/counts.js +81 -0
- package/modules/government/db.js +242 -0
- package/modules/government/decision-board.js +234 -8
- package/modules/government/decision-rules.js +2 -0
- package/modules/government/docket.js +114 -6
- package/modules/government/law-board.js +131 -0
- package/modules/government/laws.js +241 -0
- package/modules/government/leads.js +170 -0
- package/modules/government/migrations/government_027_laws.sql +143 -0
- package/modules/government/migrations/government_028_asks.sql +57 -0
- package/modules/government/migrations/government_029_leads.sql +135 -0
- package/modules/government/migrations/government_030_buildouts.sql +111 -0
- package/modules/government/migrations/government_031_treasury.sql +107 -0
- package/modules/government/module.json +2 -1
- package/modules/government/pollers/treasury-borrow.js +62 -0
- package/modules/government/positions.js +107 -0
- package/modules/government/presets.js +149 -0
- package/modules/government/routes/government.js +425 -2
- package/modules/government/routing.js +154 -0
- package/modules/government/treasury-db.js +166 -0
- package/modules/government/treasury.js +241 -0
- package/modules/hall-ui/public/atlas.html +2 -2
- package/modules/hall-ui/public/blockers.html +2 -2
- package/modules/hall-ui/public/board-room.html +2 -2
- package/modules/hall-ui/public/board-room.js +2 -0
- package/modules/hall-ui/public/city-draw.js +604 -11
- package/modules/hall-ui/public/city.css +157 -1
- package/modules/hall-ui/public/city.html +37 -7
- package/modules/hall-ui/public/city.js +441 -21
- package/modules/hall-ui/public/city.states.json +110 -5
- package/modules/hall-ui/public/collab.html +2 -2
- package/modules/hall-ui/public/commands.html +2 -2
- package/modules/hall-ui/public/copy-desk.html +2 -2
- package/modules/hall-ui/public/craft-mode.js +112 -29
- package/modules/hall-ui/public/deploy.html +2 -2
- package/modules/hall-ui/public/diagram-viewer.css +12 -0
- package/modules/hall-ui/public/diagram-viewer.js +4 -0
- package/modules/hall-ui/public/diagrams.html +2 -2
- package/modules/hall-ui/public/drachmae.html +2 -2
- package/modules/hall-ui/public/fleet.html +2 -2
- package/modules/hall-ui/public/gate.html +2 -2
- package/modules/hall-ui/public/goal-map.html +2 -2
- package/modules/hall-ui/public/goals.html +2 -2
- package/modules/hall-ui/public/gov-feeds.js +141 -0
- package/modules/hall-ui/public/gov-living.js +1002 -0
- package/modules/hall-ui/public/gov-positions.js +97 -0
- package/modules/hall-ui/public/government.html +2 -2
- package/modules/hall-ui/public/hall-render.js +3 -0
- package/modules/hall-ui/public/idea.html +2 -2
- package/modules/hall-ui/public/ideas.html +2 -2
- package/modules/hall-ui/public/index.html +3 -3
- package/modules/hall-ui/public/modules.html +2 -2
- package/modules/hall-ui/public/my-agents.css +334 -0
- package/modules/hall-ui/public/my-agents.html +72 -0
- package/modules/hall-ui/public/my-agents.js +603 -0
- package/modules/hall-ui/public/my-agents.states.json +37 -0
- package/modules/hall-ui/public/primer.html +2 -2
- package/modules/hall-ui/public/profile-connections.js +40 -28
- package/modules/hall-ui/public/profile.html +2 -2
- package/modules/hall-ui/public/project-settings.html +2 -2
- package/modules/hall-ui/public/ranks.html +2 -2
- package/modules/hall-ui/public/roadmap.html +2 -2
- package/modules/hall-ui/public/room.css +8 -0
- package/modules/hall-ui/public/roster.html +2 -2
- package/modules/hall-ui/public/sessions.html +2 -2
- package/modules/hall-ui/public/settings.html +2 -2
- package/modules/hall-ui/public/shell.js +116 -16
- package/modules/hall-ui/public/street-card.js +18 -1
- package/modules/hall-ui/public/street-deck.css +299 -0
- package/modules/hall-ui/public/street-deck.js +1145 -0
- package/modules/hall-ui/public/street-depth.js +712 -0
- package/modules/hall-ui/public/street-desk.js +5 -2
- package/modules/hall-ui/public/street-draw.js +109 -16
- package/modules/hall-ui/public/street-inside.js +137 -22
- package/modules/hall-ui/public/street-office.js +705 -0
- package/modules/hall-ui/public/street.css +161 -11
- package/modules/hall-ui/public/street.html +41 -11
- package/modules/hall-ui/public/street.js +78 -66
- package/modules/hall-ui/public/street.states.json +129 -21
- package/modules/hall-ui/public/studio.html +2 -2
- package/modules/hall-ui/public/style.css +5 -1
- package/modules/hall-ui/public/task.html +2 -2
- package/modules/hall-ui/public/thinking.html +2 -2
- package/modules/hall-ui/public/tweak-editor.html +2 -2
- package/modules/hall-ui/public/watch.html +2 -2
- package/modules/hall-ui/public/watch.js +14 -1
- package/modules/hall-ui/public/work.html +2 -2
- package/modules/hall-ui/records/living-city.md +57 -0
- package/modules/hall-ui/records/my-agents.md +21 -0
- package/modules/hall-ui/records/oversight-family.md +1 -0
- package/modules/hall-ui/records/profile-connections.md +10 -0
- package/modules/hall-ui/records/reading-rooms.md +2 -0
- package/modules/ideas/routes/blockers.js +92 -3
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/moving.js +17 -1
- package/modules/lifecycle/routes/moving.js +6 -0
- package/modules/lifecycle/routes/project-settings.js +23 -0
- package/modules/onboarding/approval-broadcast.js +8 -0
- package/modules/onboarding/routes/access-requests.js +16 -6
- package/modules/platform-identity/connections.js +13 -2
- package/modules/platform-identity/routes/connections.js +7 -1
- package/modules/public-landing/public/account-private.states.json +2 -2
- package/modules/public-landing/public/account.html +65 -5
- package/modules/public-landing/public/account.states.json +3 -3
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
- package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
- package/modules/public-landing/public/assets/world.css +4 -4
- package/modules/public-landing/public/connections-empty.states.json +14 -0
- package/modules/public-landing/public/connections-inactive.states.json +14 -0
- package/modules/public-landing/public/connections.html +450 -0
- package/modules/public-landing/public/connections.probes.json +414 -0
- package/modules/public-landing/public/connections.states.json +17 -0
- package/modules/public-landing/public/contact.html +2 -2
- package/modules/public-landing/public/guild.html +2 -2
- package/modules/public-landing/public/index.html +2 -2
- package/modules/public-landing/public/invitations.html +2 -2
- package/modules/public-landing/public/privacy.html +2 -2
- package/modules/public-landing/public/projects.html +2 -2
- package/modules/public-landing/public/projects.probes.json +6 -2
- package/modules/public-landing/public/terms.html +2 -2
- package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
- package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
- package/modules/ui-design/kit/fixtures/connections.json +22 -0
- package/modules/ui-design/kit/serve.js +95 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +128 -0
- package/scripts/discord/README.md +8 -5
- package/scripts/discord/post.js +12 -1
- package/scripts/gds/artifact-audit.js +20 -3
- package/scripts/gds/ask.js +164 -0
- package/scripts/gds/autobongos-run.js +20 -5
- package/scripts/gds/autobongos-tweak-lane.js +262 -0
- package/scripts/gds/autobongos-verify.js +21 -2
- package/scripts/gds/claim.js +2 -0
- package/scripts/gds/government-walk.js +638 -4
- package/scripts/gds/pulse.js +9 -6
- package/scripts/gds/ship-escalation.js +22 -3
- package/scripts/gds/ship-finish.js +5 -4
- package/scripts/gds/ship-merge-gate.js +18 -6
- package/scripts/gds/tweak-halt.js +1 -0
- package/scripts/hall-preview/README.md +41 -1
- package/scripts/hall-preview/pref-writes.js +140 -0
- package/scripts/hall-preview/refresh-fixtures.js +101 -7
- package/scripts/hall-preview/scrub-identity.js +366 -7
- package/scripts/hall-preview/server.js +27 -1
- package/scripts/render-diagrams.sh +4 -2
- package/src/bongos/auth-idp.js +6 -1
- package/src/bongos/project-settings.js +41 -0
- package/src/bongos/route-rank-check.js +27 -0
- package/src/bongos/routes/me.js +68 -7
- package/src/bongos/serve-internal.js +5 -1
- package/src/module-api.js +7 -1
- package/src/platform-server.js +10 -0
- package/tests/application_lifecycle.mjs +47 -5
- package/tests/autobongos_tweak_lane.mjs +208 -0
- package/tests/conductor_main_e2e.mjs +21 -0
- package/tests/connections_api.mjs +18 -0
- package/tests/craft_mode_nav.mjs +5 -3
- package/tests/craft_mode_tallies.mjs +52 -16
- package/tests/discord_approvals.mjs +11 -0
- package/tests/discord_channels.mjs +20 -0
- package/tests/fixtures/gov-seated.mjs +64 -0
- package/tests/fixtures/government-ask-cards.json +1284 -0
- package/tests/government_act_alone_buildings.mjs +1 -0
- package/tests/government_agent_asks.mjs +322 -0
- package/tests/government_asks.mjs +408 -0
- package/tests/government_build_rights.mjs +253 -0
- package/tests/government_buildouts.mjs +382 -0
- package/tests/government_counts.mjs +143 -0
- package/tests/government_decision_rules.mjs +5 -1
- package/tests/government_laws.mjs +488 -0
- package/tests/government_laws_session.mjs +321 -0
- package/tests/government_leads.mjs +375 -0
- package/tests/government_positions.mjs +127 -0
- package/tests/government_presets.mjs +215 -0
- package/tests/government_routes.mjs +8 -1
- package/tests/government_routing.mjs +118 -0
- package/tests/government_seed.mjs +5 -0
- package/tests/government_treasury.mjs +394 -0
- package/tests/governor_docket.mjs +26 -2
- package/tests/hall_buildouts.mjs +208 -0
- package/tests/hall_city.mjs +14 -6
- package/tests/hall_city_living.mjs +617 -0
- package/tests/hall_gov_counts.mjs +336 -0
- package/tests/hall_gov_living.mjs +636 -0
- package/tests/hall_gov_real_site.mjs +655 -0
- package/tests/hall_matter_card.mjs +6 -1
- package/tests/hall_my_agents.mjs +481 -0
- package/tests/hall_nav.mjs +107 -4
- package/tests/hall_page_gate_map.mjs +4 -0
- package/tests/hall_preview_governor_fixtures.mjs +30 -3
- package/tests/hall_preview_scrub.mjs +59 -1
- package/tests/hall_preview_security.mjs +169 -0
- package/tests/hall_reading_world.mjs +44 -0
- package/tests/hall_roster_world.mjs +6 -2
- package/tests/hall_street.mjs +18 -2
- package/tests/hall_street_amend.mjs +1 -1
- package/tests/hall_street_deck.mjs +609 -0
- package/tests/hall_street_depth.mjs +559 -0
- package/tests/hall_street_desk.mjs +1 -1
- package/tests/hall_street_inside.mjs +29 -5
- package/tests/hall_tweak_editor.mjs +4 -1
- package/tests/hub_account_page.mjs +94 -3
- package/tests/hub_connections_page.mjs +507 -0
- package/tests/hub_invite_badge.mjs +1 -1
- package/tests/idp_device_exchange.mjs +18 -0
- package/tests/landing_legal_pages.mjs +8 -5
- package/tests/landing_page.mjs +23 -4
- package/tests/nav_permission_atoms.mjs +4 -1
- package/tests/profile_connections_ui.mjs +88 -25
- package/tests/projects_hub.mjs +15 -0
- package/tests/render_check.mjs +2 -2
- package/tests/ship_broadcast_routing.mjs +57 -16
- package/tests/ship_broadcast_server.mjs +40 -7
- package/tests/ship_merge_gate.mjs +19 -0
- package/tests/ui_design_kit.mjs +2 -2
- package/tests/watch_applications_queue.mjs +23 -0
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
have no seam. Light is the same sculpture cut out with real alpha, standing
|
|
20
20
|
on paper under a contact shadow — no black panel anywhere.
|
|
21
21
|
|
|
22
|
-
MATERIALS. Six rendered plates, in pairs: each black-ground
|
|
22
|
+
MATERIALS. Six rendered plates, in pairs: each black-ground WebP for dark and
|
|
23
23
|
its alpha cutout for light (`--plate-set` / `--plate-set-cut`, `--plate-bead`
|
|
24
24
|
/ `--plate-bead-cut`, `--plate-bongos` / `--plate-bongos-cut`). Every path is
|
|
25
25
|
ROOT-ABSOLUTE: Chromium resolves a url() inside a custom property against the
|
|
@@ -67,11 +67,11 @@ html { color-scheme: light; }
|
|
|
67
67
|
--font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;
|
|
68
68
|
|
|
69
69
|
/* ---- derived: the plates, in pairs (black-ground plate / alpha cutout) ---- */
|
|
70
|
-
--plate-set: url(/assets/world/r2-chrome-blobs-set.
|
|
70
|
+
--plate-set: url(/assets/world/r2-chrome-blobs-set.webp);
|
|
71
71
|
--plate-set-cut: url(/assets/world/r2-chrome-blobs-set-cut.webp);
|
|
72
|
-
--plate-bead: url(/assets/world/r2-chrome-blob-one.
|
|
72
|
+
--plate-bead: url(/assets/world/r2-chrome-blob-one.webp);
|
|
73
73
|
--plate-bead-cut: url(/assets/world/r2-chrome-blob-one-cut.webp);
|
|
74
|
-
--plate-bongos: url(/assets/world/r2-bongos-chrome-b.
|
|
74
|
+
--plate-bongos: url(/assets/world/r2-bongos-chrome-b.webp);
|
|
75
75
|
--plate-bongos-cut: url(/assets/world/r2-bongos-chrome-a-cut.webp);
|
|
76
76
|
--blob-blend: lighten;
|
|
77
77
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "The connections page with no connections and nothing pending (task 1004278): the graph is fixtures/connections-empty.json, so the asking section and the sent list stay down, the count line says how to start, and builders you may know still show with their reasons.",
|
|
3
|
+
"page": "/connections",
|
|
4
|
+
"surface": "public-landing",
|
|
5
|
+
"modes": ["dark"],
|
|
6
|
+
"modeQuery": false,
|
|
7
|
+
"stub": { "fixtures": { "connections": "connections-empty" } },
|
|
8
|
+
"ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/me/guild-"],
|
|
9
|
+
"ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "Unauthorized"],
|
|
10
|
+
"ignoreControls": [".vh"],
|
|
11
|
+
"states": {
|
|
12
|
+
"in-empty": { "auth": true, "actions": [["wait", 500]], "expect": { "visible": ["#connBody", "#connCount", "#connSuggest", "#askHandle"], "hidden": ["#connAsks", "#sentH", "#door", "#loading"], "count": [["#connList .mineRow", 0], ["#suggestList .mineRow", 2]], "text": [["#connCount", "No connections yet. Accept a request above, or ask a builder below."]] } }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_": "The connections page for a builder whose platform profile is not active yet (task 1004278): fixtures/connections-inactive.json answers every connections route 403 profile_provisional, the refusal about the VIEWER (ADR 0202 D4 §1), so the page says when connections open and shows no lists.",
|
|
3
|
+
"page": "/connections",
|
|
4
|
+
"surface": "public-landing",
|
|
5
|
+
"modes": ["dark"],
|
|
6
|
+
"modeQuery": false,
|
|
7
|
+
"stub": { "fixtures": { "connections": "connections-inactive" } },
|
|
8
|
+
"ignoreRequests": ["/api/(gds|bongos)/me$", "/my-projects", "/me/guild-", "/api/(gds|bongos)/connections"],
|
|
9
|
+
"ignoreConsole": ["/api/(gds|bongos)/me", "\\b401\\b", "\\b403\\b", "Unauthorized", "Forbidden"],
|
|
10
|
+
"ignoreControls": [".vh"],
|
|
11
|
+
"states": {
|
|
12
|
+
"in-inactive": { "auth": true, "actions": [["wait", 500]], "expect": { "visible": ["#inactive"], "hidden": ["#connBody", "#door", "#loading", "#readFailed"] } }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-mode="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<!-- THE CONNECTIONS PAGE (task 1004278; the owner's calls of 2026-09-25, ADR 0337
|
|
7
|
+
D5.4). The hub is the primary home for connections: the builders you know
|
|
8
|
+
across every project, the requests in both directions, asking a builder by
|
|
9
|
+
handle, and "Builders you may know". A project hall's connections tab narrows
|
|
10
|
+
to the people who also build there (task 1004279); this page is the whole set.
|
|
11
|
+
Hub-only, like /account and /invitations: platform-server.js serves
|
|
12
|
+
/connections only where platform-identity is on, because every read and write
|
|
13
|
+
below is one of that module's own-scoped routes (modules/platform-identity/
|
|
14
|
+
routes/connections.js — GET /connections, GET /connections/requests, GET
|
|
15
|
+
/connections/suggestions, POST /connections, POST /connections/:id/accept,
|
|
16
|
+
POST /connections/:id/decline, DELETE /connections/:id). No new scoring and no
|
|
17
|
+
new data (ADR 0202, 0207): the server picks every suggestion and its reason.
|
|
18
|
+
Server-stamped like the footer doors: {{…}} tokens filled by landingPage(). -->
|
|
19
|
+
<title>{{productName}} — Connections</title>
|
|
20
|
+
<meta name="description" content="Your {{productName}} connections: the builders you know across every project, requests to connect, and builders you may know.">
|
|
21
|
+
<meta name="robots" content="noindex">
|
|
22
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
23
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
24
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400&family=Manrope:wght@300;400;500;600&display=swap">
|
|
25
|
+
<link rel="stylesheet" href="/assets/cosmos.css">
|
|
26
|
+
<!-- Dark only, like every apex page (ADR 0204): data-mode="dark" is fixed on <html>. -->
|
|
27
|
+
<style>
|
|
28
|
+
/* The page's own frame, in the invitations page's voice (a lowercase light
|
|
29
|
+
heading and one lede); the rows, the handle field, the note and the buttons
|
|
30
|
+
are cosmos.css's own rules (.mineRows, .mineJoinRow, .mineJoinNote, .btn).
|
|
31
|
+
No :root block — the pack's injected theme owns the tokens. */
|
|
32
|
+
/* the skip link un-hides itself on focus, reversing everything .vh sets (the hub's rule) */
|
|
33
|
+
.skip:focus{position:fixed;top:8px;left:8px;z-index:100;width:auto;height:auto;margin:0;overflow:visible;clip-path:none;white-space:normal;
|
|
34
|
+
font-size:14px;font-weight:800;text-decoration:none;color:var(--ink);background:var(--bg-card);
|
|
35
|
+
border:2px solid var(--ctl-line);border-radius:12px;padding:10px 16px;}
|
|
36
|
+
.acct{position:relative;z-index:2;max-width:1060px;margin:0 auto;padding:clamp(6px,1.5vw,18px) clamp(16px,4vw,40px) 56px;}
|
|
37
|
+
.acct h1{font-family:var(--font-display);font-weight:300;font-size:clamp(28px,3.4vw,44px);letter-spacing:-.035em;line-height:1.04;text-transform:lowercase;color:var(--ink);margin:18px 0 0;}
|
|
38
|
+
.acct>.lede{margin:8px 0 18px;max-width:62ch;font-size:15.5px;line-height:1.55;color:var(--ink-soft);font-weight:400;}
|
|
39
|
+
.acct .loading{font-size:15px;color:var(--ink-soft);}
|
|
40
|
+
.aDoor{border-top:1px solid var(--rule);padding:30px 0 10px;max-width:62ch;}
|
|
41
|
+
.aDoor p{margin:0 0 18px;font-size:15.5px;line-height:1.55;color:var(--ink-soft);}
|
|
42
|
+
.aDoor .gh{width:18px;height:18px;fill:currentColor;}
|
|
43
|
+
.aDoor .btn{text-transform:none;}
|
|
44
|
+
.acct .mineEmpty{font-weight:400;color:var(--ink-soft);padding:0 0 6px;max-width:62ch;}
|
|
45
|
+
.mineExtra section:first-child{margin-top:12px;}
|
|
46
|
+
/* nothing is asking: the connections list is the first thing on the page */
|
|
47
|
+
#connAsks[hidden]+section{margin-top:12px;}
|
|
48
|
+
#connMsg{margin:0 0 4px;}
|
|
49
|
+
/* one builder: their avatar, then the name over the @handle in the rows' mono face */
|
|
50
|
+
.connWho{display:flex;align-items:center;gap:12px;min-width:0;flex:1 1 auto;}
|
|
51
|
+
.connAv{width:36px;height:36px;border-radius:50%;flex:none;object-fit:cover;background:color-mix(in srgb,var(--ink) 12%,transparent);}
|
|
52
|
+
/* a request's note is the asker's own words: kept as typed, wrapped, never linked */
|
|
53
|
+
.connNote{margin:6px 0 0;max-width:62ch;font-size:13.5px;line-height:1.5;color:var(--ink-soft);white-space:pre-wrap;overflow-wrap:anywhere;}
|
|
54
|
+
/* cosmos.css drops a row's actions to their own line under 480px, for the two-button
|
|
55
|
+
rows; a row with ONE button (Remove, Connect, Withdraw) keeps it beside the name */
|
|
56
|
+
@media (max-width:480px){.mineRow.connOne{flex-wrap:nowrap;}.connOne .mineRow__actions{flex-basis:auto;margin-top:0;}}
|
|
57
|
+
.connSub{margin:26px 0 4px;font-size:14px;font-weight:600;color:var(--ink-soft);}
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<!-- THE BAR and its two bands are the hub's own (projects.html, task 1004455):
|
|
62
|
+
same markup, same cosmos.css rules, so /invitations → /connections keeps the
|
|
63
|
+
chrome. No map search (it only filters the map), and the band links name
|
|
64
|
+
/projects in full because this page has no views of its own. assets/hub-bar.js
|
|
65
|
+
runs it, shared with /account, /invitations and the three footer doors. -->
|
|
66
|
+
<a class="skip vh" href="#conn">Skip to content</a>
|
|
67
|
+
<header class="shell bar">
|
|
68
|
+
<div class="barLeft">
|
|
69
|
+
<a class="barMark" id="barMark" href="/">
|
|
70
|
+
<svg viewBox="0 0 26 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.4"
|
|
71
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
72
|
+
<ellipse cx="8.4" cy="5.6" rx="6.6" ry="2.9"/>
|
|
73
|
+
<path d="M1.8 5.6v6.6a6.6 2.9 0 0 0 13.2 0V5.6"/>
|
|
74
|
+
<ellipse cx="19.6" cy="8.4" rx="4.7" ry="2.1"/>
|
|
75
|
+
<path d="M14.9 8.4v4.6a4.7 2.1 0 0 0 9.4 0V8.4"/>
|
|
76
|
+
</svg>
|
|
77
|
+
<span>{{productName}}</span>
|
|
78
|
+
</a>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="barRight">
|
|
81
|
+
<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
|
|
82
|
+
<!-- the identity chip IS the profile trigger; assets/hub-bar.js fills it from GET /me.
|
|
83
|
+
Signed out it stays hidden and the sign-in door takes its slot. -->
|
|
84
|
+
<button class="trig who" type="button" id="who" data-menu="profile" aria-expanded="false" aria-controls="menuProfile" hidden><img id="whoImg" alt="" hidden><span class="whoName" id="whoLogin"></span><svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
|
|
85
|
+
<a class="signinDoor" id="signinTop" hidden href="/api/bongos/auth/web/start?return=/connections&repos=1">Sign in</a>
|
|
86
|
+
</div>
|
|
87
|
+
</header>
|
|
88
|
+
|
|
89
|
+
<div class="menuBand" id="menuProjects" data-menu="projects" hidden>
|
|
90
|
+
<div class="bandIn">
|
|
91
|
+
<ul class="bandRow">
|
|
92
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=map">Sky</a><span class="bandC">the sky of every project</span></li>
|
|
93
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
94
|
+
<li class="bandCell" id="mineCell"><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own</span></li>
|
|
95
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
96
|
+
</ul>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="menuBand" id="menuProfile" data-menu="profile" hidden>
|
|
100
|
+
<div class="bandIn bandProfile">
|
|
101
|
+
<p class="bandWho" id="bandWho"><img id="bandWhoImg" alt="" hidden><span class="whoName" id="bandWhoLogin"></span></p>
|
|
102
|
+
<ul class="bandRow">
|
|
103
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=mine">My projects</a></li>
|
|
104
|
+
<li class="bandCell"><a class="bandT" href="/invitations">Invitations</a></li><li class="bandCell"><a class="bandT" href="/connections" aria-current="page">Connections</a></li><li class="bandCell"><a class="bandT" href="/account">Account</a></li>
|
|
105
|
+
<!-- the honest hover target; the delegated click upgrades it to the POST -->
|
|
106
|
+
<li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
|
|
107
|
+
</ul>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<main class="acct" id="conn">
|
|
112
|
+
<h1>connections</h1>
|
|
113
|
+
<p class="lede">The builders you know, across every project. A connection is a way to find each other again; it grants no rank and no access to anything.</p>
|
|
114
|
+
|
|
115
|
+
<!-- signed-out door: revealed when GET /me answers nobody, the /account shape -->
|
|
116
|
+
<section class="aDoor" id="door" hidden aria-labelledby="door-h">
|
|
117
|
+
<h2 class="vh" id="door-h">Sign in</h2>
|
|
118
|
+
<p>Sign in with GitHub to see your connections and the builders asking to connect.</p>
|
|
119
|
+
<a class="btn" id="signin" href="/api/bongos/auth/web/start?return=/connections&repos=1">
|
|
120
|
+
<!-- keep the spaces inside this path's numbers: same shape, and they are load-bearing (task 1004401) -->
|
|
121
|
+
<svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23 .82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2 .82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12 .51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29 .25.54.73 .54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15 .46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
|
|
122
|
+
Sign in with GitHub
|
|
123
|
+
</a>
|
|
124
|
+
</section>
|
|
125
|
+
|
|
126
|
+
<p class="loading" id="loading">Reading your connections…</p>
|
|
127
|
+
<!-- a 403 from the connections routes is about the VIEWER: their platform
|
|
128
|
+
profile is not active yet (ADR 0202 D4 §1), so they cannot take part -->
|
|
129
|
+
<p class="mineEmpty" id="inactive" hidden>Connections open once your profile is active. It turns on the first time you sign in to a project with your {{productName}} account, in your browser.</p>
|
|
130
|
+
<p class="projMsg err" id="readFailed" role="alert" hidden>Your connections could not be read. Reload the page to try again.</p>
|
|
131
|
+
|
|
132
|
+
<!-- what the last Accept, Decline, Remove, Withdraw or Connect did; down while it says nothing -->
|
|
133
|
+
<p class="projMsg" id="connMsg" role="status" aria-live="polite" hidden></p>
|
|
134
|
+
|
|
135
|
+
<div class="mineExtra" id="connBody" hidden>
|
|
136
|
+
<!-- requests addressed to you: Accept or Decline. A decline is silent — the
|
|
137
|
+
asker is never told (ADR 0202 D2) — and when you read one is never shown. -->
|
|
138
|
+
<section id="connAsks" aria-labelledby="connAsksH" hidden>
|
|
139
|
+
<h2 id="connAsksH">Asking to connect</h2>
|
|
140
|
+
<ul class="mineRows" id="incomingList"></ul>
|
|
141
|
+
</section>
|
|
142
|
+
|
|
143
|
+
<section aria-labelledby="connListH">
|
|
144
|
+
<h2 id="connListH">Your connections</h2>
|
|
145
|
+
<p class="mineEmpty" id="connCount"></p>
|
|
146
|
+
<ul class="mineRows" id="connList"></ul>
|
|
147
|
+
</section>
|
|
148
|
+
|
|
149
|
+
<!-- "Builders you may know" (ADR 0202 D5, ADR 0337 D4.2): the server's own
|
|
150
|
+
suggestions and their one-line reasons, rendered as they come. One panel:
|
|
151
|
+
the hub community page links here rather than drawing a second one. -->
|
|
152
|
+
<section id="connSuggest" aria-labelledby="connSuggestH" hidden>
|
|
153
|
+
<h2 id="connSuggestH">Builders you may know</h2>
|
|
154
|
+
<ul class="mineRows" id="suggestList"></ul>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
157
|
+
<section aria-labelledby="connAskH">
|
|
158
|
+
<h2 id="connAskH">Connect with a builder</h2>
|
|
159
|
+
<p class="lede">Ask by their handle, the name after the @.</p>
|
|
160
|
+
<div class="mineJoinRow">
|
|
161
|
+
<label class="vh" for="askHandle">Their handle</label>
|
|
162
|
+
<input id="askHandle" type="text" placeholder="Their handle" maxlength="40" autocomplete="off" autocapitalize="off" spellcheck="false">
|
|
163
|
+
<button id="askBtn" type="button" class="btn small">Connect</button>
|
|
164
|
+
</div>
|
|
165
|
+
<label class="vh" for="askNote">Add a note (optional)</label>
|
|
166
|
+
<textarea id="askNote" class="mineJoinNote" rows="2" maxlength="280" placeholder="A note (optional): how you know each other."></textarea>
|
|
167
|
+
<p class="projMsg" id="askStatus" role="status" aria-live="polite"></p>
|
|
168
|
+
<h3 class="connSub" id="sentH" hidden>Requests you have sent</h3>
|
|
169
|
+
<ul class="mineRows" id="outgoingList"></ul>
|
|
170
|
+
</section>
|
|
171
|
+
</div>
|
|
172
|
+
</main>
|
|
173
|
+
|
|
174
|
+
<footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a></nav></footer>
|
|
175
|
+
|
|
176
|
+
<script src="/assets/hub-bar.js"></script>
|
|
177
|
+
<script>
|
|
178
|
+
(function () {
|
|
179
|
+
'use strict';
|
|
180
|
+
var API = '/api/bongos';
|
|
181
|
+
// Boundary-forced copies of the route's own limits (modules/platform-identity/
|
|
182
|
+
// routes/connections.js: a handle is 1–39 characters, a note at most
|
|
183
|
+
// connections.js NOTE_MAX_CHARS); tests/hub_connections_page.mjs holds them equal.
|
|
184
|
+
var HANDLE_MAX = 39;
|
|
185
|
+
var NOTE_MAX = 280;
|
|
186
|
+
function $(id) { return document.getElementById(id); }
|
|
187
|
+
// The bar's chip, bands and sign out are assets/hub-bar.js (task 1004489), loaded
|
|
188
|
+
// just above; its `ready` settles with who GET /me says is signed in, and that is
|
|
189
|
+
// what picks the page's state. Without the asset nobody is signed in here.
|
|
190
|
+
var bar = window.HubBar || { paint: function () {}, ready: Promise.resolve(null) };
|
|
191
|
+
|
|
192
|
+
/* FOUR THINGS THIS PAGE MUST NOT DO — the hall's profile panel keeps the same
|
|
193
|
+
four (modules/hall-ui/public/profile-connections.js), and the UI is the last
|
|
194
|
+
place each privacy decision of ADR 0202 / 0207 could be undone:
|
|
195
|
+
1. claim more than a sent request's `201 { status: 'pending' }` supports —
|
|
196
|
+
"Request sent." is the whole claim, the same words whether the server
|
|
197
|
+
wrote the row or swallowed a re-ask inside the decline cooldown;
|
|
198
|
+
2. show when an incoming request was read (`seen_at`) — a screenshot
|
|
199
|
+
passed between the two people would carry it back to the asker;
|
|
200
|
+
3. print a count that is not the length of the list beside it;
|
|
201
|
+
4. rank, filter or re-word a suggestion — the server chose each one and
|
|
202
|
+
its reason ("N connections in common", a shared project). */
|
|
203
|
+
|
|
204
|
+
function el(tag, cls, text) {
|
|
205
|
+
var e = document.createElement(tag);
|
|
206
|
+
if (cls) e.className = cls;
|
|
207
|
+
if (text != null) e.textContent = text;
|
|
208
|
+
return e;
|
|
209
|
+
}
|
|
210
|
+
function arr(v) { return Array.isArray(v) ? v : []; }
|
|
211
|
+
function plural(n, word) { return n + ' ' + word + (n === 1 ? '' : 's'); }
|
|
212
|
+
function safeHttpUrl(raw) {
|
|
213
|
+
try {
|
|
214
|
+
var u = new URL(String(raw));
|
|
215
|
+
return (u.protocol === 'https:' || u.protocol === 'http:') ? u.href : '';
|
|
216
|
+
} catch (e) { return ''; }
|
|
217
|
+
}
|
|
218
|
+
function day(iso) {
|
|
219
|
+
var d = new Date(iso);
|
|
220
|
+
return isNaN(d.getTime()) ? '' : d.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
|
|
221
|
+
}
|
|
222
|
+
function say(node, text, kind) {
|
|
223
|
+
node.textContent = text || '';
|
|
224
|
+
node.className = 'projMsg' + (kind ? ' ' + kind : '');
|
|
225
|
+
if (node.id === 'connMsg') node.hidden = !text;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── one builder, as a row ──────────────────────────────────────────────────
|
|
229
|
+
// Only the three columns the API projects for a counterparty (handle,
|
|
230
|
+
// display_name, avatar_url): nothing rollup-derived, so nothing here for
|
|
231
|
+
// hide_stats to suppress (ADR 0202 D4 §3). No link: there is no public profile
|
|
232
|
+
// page to point at yet (/u/<handle> is unbuilt, canonical-profile.js).
|
|
233
|
+
function personRow(p) {
|
|
234
|
+
var row = el('li', 'mineRow');
|
|
235
|
+
var who = el('div', 'connWho');
|
|
236
|
+
var src = safeHttpUrl(p.avatar_url);
|
|
237
|
+
var av;
|
|
238
|
+
if (src) { av = el('img', 'connAv'); av.src = src; av.alt = ''; }
|
|
239
|
+
else { av = el('span', 'connAv'); av.setAttribute('aria-hidden', 'true'); }
|
|
240
|
+
who.appendChild(av);
|
|
241
|
+
var main = el('div', 'mineRow__main');
|
|
242
|
+
main.appendChild(el('div', 'mineRow__name', p.display_name || p.handle || 'Builder'));
|
|
243
|
+
if (p.handle) main.appendChild(el('span', 'mineRow__host', '@' + p.handle));
|
|
244
|
+
who.appendChild(main);
|
|
245
|
+
row.appendChild(who);
|
|
246
|
+
var actions = el('div', 'mineRow__actions');
|
|
247
|
+
row.appendChild(actions);
|
|
248
|
+
return { row: row, main: main, actions: actions };
|
|
249
|
+
}
|
|
250
|
+
function button(label, ghost) {
|
|
251
|
+
var b = el('button', ghost ? 'btn small ghost' : 'btn small', label);
|
|
252
|
+
b.type = 'button';
|
|
253
|
+
return b;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── the reads ──────────────────────────────────────────────────────────────
|
|
257
|
+
function read(path) {
|
|
258
|
+
return fetch(API + path, { credentials: 'same-origin' }).then(function (res) {
|
|
259
|
+
return res.json().catch(function () { return {}; }).then(function (data) {
|
|
260
|
+
return { ok: res.ok, status: res.status, data: data || {} };
|
|
261
|
+
});
|
|
262
|
+
}).catch(function () { return { ok: false, status: 0, data: {} }; });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// The four lists, from the server's answer and nothing else: every action
|
|
266
|
+
// below re-reads rather than patching the rows it just changed.
|
|
267
|
+
function paint(lists) {
|
|
268
|
+
var incoming = lists.incoming, outgoing = lists.outgoing;
|
|
269
|
+
var connections = lists.connections, suggestions = lists.suggestions;
|
|
270
|
+
|
|
271
|
+
var inUl = $('incomingList');
|
|
272
|
+
inUl.textContent = '';
|
|
273
|
+
incoming.forEach(function (r) {
|
|
274
|
+
var p = personRow(r);
|
|
275
|
+
if (r.note) p.main.appendChild(el('p', 'connNote', r.note));
|
|
276
|
+
var accept = button('Accept');
|
|
277
|
+
var decline = button('Decline', true);
|
|
278
|
+
accept.addEventListener('click', function () {
|
|
279
|
+
act('POST', '/connections/' + encodeURIComponent(r.id) + '/accept', 'Connected.', [accept, decline]);
|
|
280
|
+
});
|
|
281
|
+
decline.addEventListener('click', function () {
|
|
282
|
+
act('POST', '/connections/' + encodeURIComponent(r.id) + '/decline', 'Declined. They are not told.', [accept, decline]);
|
|
283
|
+
});
|
|
284
|
+
p.actions.appendChild(accept);
|
|
285
|
+
p.actions.appendChild(decline);
|
|
286
|
+
inUl.appendChild(p.row);
|
|
287
|
+
});
|
|
288
|
+
$('connAsks').hidden = !incoming.length;
|
|
289
|
+
|
|
290
|
+
var list = $('connList');
|
|
291
|
+
list.textContent = '';
|
|
292
|
+
$('connCount').textContent = connections.length
|
|
293
|
+
? plural(connections.length, 'connection') + '.'
|
|
294
|
+
: 'No connections yet. Accept a request above, or ask a builder below.';
|
|
295
|
+
connections.forEach(function (c) {
|
|
296
|
+
var p = personRow(c);
|
|
297
|
+
var when = c.connected_at ? day(c.connected_at) : '';
|
|
298
|
+
if (when) p.main.appendChild(el('p', 'mineRow__hint', 'Connected ' + when));
|
|
299
|
+
var remove = button('Remove', true);
|
|
300
|
+
remove.addEventListener('click', function () {
|
|
301
|
+
act('DELETE', '/connections/' + encodeURIComponent(c.id), 'Connection removed.', [remove]);
|
|
302
|
+
});
|
|
303
|
+
p.actions.appendChild(remove);
|
|
304
|
+
p.row.className += ' connOne';
|
|
305
|
+
list.appendChild(p.row);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
var sug = $('suggestList');
|
|
309
|
+
sug.textContent = '';
|
|
310
|
+
suggestions.forEach(function (s) {
|
|
311
|
+
if (!s || !s.handle) return;
|
|
312
|
+
var p = personRow(s);
|
|
313
|
+
if (s.reason) p.main.appendChild(el('p', 'mineRow__hint', s.reason));
|
|
314
|
+
var ask = button('Connect');
|
|
315
|
+
ask.addEventListener('click', function () {
|
|
316
|
+
ask.disabled = true;
|
|
317
|
+
connect(s.handle, null).then(function (out) {
|
|
318
|
+
say($('connMsg'), out.text, out.kind);
|
|
319
|
+
if (out.kind === 'ok') return load();
|
|
320
|
+
ask.disabled = false;
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
p.actions.appendChild(ask);
|
|
324
|
+
p.row.className += ' connOne';
|
|
325
|
+
sug.appendChild(p.row);
|
|
326
|
+
});
|
|
327
|
+
$('connSuggest').hidden = !sug.children.length;
|
|
328
|
+
|
|
329
|
+
var out = $('outgoingList');
|
|
330
|
+
out.textContent = '';
|
|
331
|
+
outgoing.forEach(function (r) {
|
|
332
|
+
var p = personRow(r);
|
|
333
|
+
p.main.appendChild(el('p', 'mineRow__hint', 'Request sent'));
|
|
334
|
+
var withdraw = button('Withdraw', true);
|
|
335
|
+
withdraw.addEventListener('click', function () {
|
|
336
|
+
act('DELETE', '/connections/' + encodeURIComponent(r.id), 'Request withdrawn.', [withdraw]);
|
|
337
|
+
});
|
|
338
|
+
p.actions.appendChild(withdraw);
|
|
339
|
+
p.row.className += ' connOne';
|
|
340
|
+
out.appendChild(p.row);
|
|
341
|
+
});
|
|
342
|
+
$('sentH').hidden = !outgoing.length;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function load() {
|
|
346
|
+
return Promise.all([read('/connections'), read('/connections/requests'), read('/connections/suggestions')]).then(function (r) {
|
|
347
|
+
var list = r[0], reqs = r[1], sugg = r[2];
|
|
348
|
+
$('loading').hidden = true;
|
|
349
|
+
if (list.status === 401 || reqs.status === 401) { showDoor(); return; }
|
|
350
|
+
if (list.status === 403 || reqs.status === 403) { show('inactive'); return; }
|
|
351
|
+
if (!list.ok || !reqs.ok) { show('readFailed'); return; }
|
|
352
|
+
show('connBody');
|
|
353
|
+
paint({
|
|
354
|
+
connections: arr(list.data.connections),
|
|
355
|
+
incoming: arr(reqs.data.incoming),
|
|
356
|
+
outgoing: arr(reqs.data.outgoing),
|
|
357
|
+
// additive, never load-bearing: a failed suggestions read only omits that panel
|
|
358
|
+
suggestions: sugg.ok ? arr(sugg.data.suggestions) : [],
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ── the writes ─────────────────────────────────────────────────────────────
|
|
364
|
+
// Accept, Decline, Remove and Withdraw: one call, then the page re-reads. A row
|
|
365
|
+
// the server no longer has (answered in another tab, expired) is one 404.
|
|
366
|
+
function act(method, path, done, btns) {
|
|
367
|
+
btns.forEach(function (b) { b.disabled = true; });
|
|
368
|
+
return fetch(API + path, { method: method, credentials: 'same-origin' }).then(function (res) {
|
|
369
|
+
if (res.status === 401) { showDoor(); return; }
|
|
370
|
+
if (res.ok) say($('connMsg'), done, 'ok');
|
|
371
|
+
else say($('connMsg'), 'That request is no longer there.', 'err');
|
|
372
|
+
return load();
|
|
373
|
+
}).catch(function () {
|
|
374
|
+
btns.forEach(function (b) { b.disabled = false; });
|
|
375
|
+
say($('connMsg'), 'That did not go through. Check your connection and try again.', 'err');
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Ask to connect. 201 is byte-identical for a request the server wrote and one
|
|
380
|
+
// it swallowed inside the decline cooldown, so "Request sent." is all it says.
|
|
381
|
+
// A 404 is an unknown, inactive and private handle alike: one answer, on purpose.
|
|
382
|
+
var CONNECT_REFUSED = {
|
|
383
|
+
connection_already_open: 'There is already a request or a connection between you.',
|
|
384
|
+
connection_target_not_found: 'No builder with that handle can be asked right now.',
|
|
385
|
+
cannot_connect_to_self: 'That is your own handle.',
|
|
386
|
+
profile_provisional: 'Your profile is not active yet, so you cannot ask anyone to connect.',
|
|
387
|
+
};
|
|
388
|
+
function connect(handle, note) {
|
|
389
|
+
var body = { handle: handle };
|
|
390
|
+
if (note) body.note = note;
|
|
391
|
+
return fetch(API + '/connections', {
|
|
392
|
+
method: 'POST',
|
|
393
|
+
credentials: 'same-origin',
|
|
394
|
+
headers: { 'content-type': 'application/json' },
|
|
395
|
+
body: JSON.stringify(body),
|
|
396
|
+
}).then(function (res) {
|
|
397
|
+
if (res.status === 201) return { text: 'Request sent.', kind: 'ok' };
|
|
398
|
+
if (res.status === 401) { showDoor(); return { text: '', kind: '' }; }
|
|
399
|
+
return res.json().catch(function () { return {}; }).then(function (data) {
|
|
400
|
+
var code = data && data.error && data.error.code;
|
|
401
|
+
return { text: CONNECT_REFUSED[code] || 'That request could not be sent. Check the handle and try again.', kind: 'err' };
|
|
402
|
+
});
|
|
403
|
+
}).catch(function () {
|
|
404
|
+
return { text: 'That did not go through. Check your connection and try again.', kind: 'err' };
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function askByHandle() {
|
|
409
|
+
var status = $('askStatus');
|
|
410
|
+
var handle = String($('askHandle').value || '').trim().replace(/^@/, '');
|
|
411
|
+
var note = String($('askNote').value || '').trim();
|
|
412
|
+
if (!handle) { say(status, 'Type a builder’s handle first.', 'err'); return; }
|
|
413
|
+
if (handle.length > HANDLE_MAX) { say(status, 'A handle is at most ' + HANDLE_MAX + ' characters.', 'err'); return; }
|
|
414
|
+
if (note.length > NOTE_MAX) { say(status, 'A note is at most ' + NOTE_MAX + ' characters.', 'err'); return; }
|
|
415
|
+
var btn = $('askBtn');
|
|
416
|
+
btn.disabled = true;
|
|
417
|
+
say(status, 'Sending…');
|
|
418
|
+
connect(handle, note || null).then(function (out) {
|
|
419
|
+
btn.disabled = false;
|
|
420
|
+
say(status, out.text, out.kind);
|
|
421
|
+
if (out.kind !== 'ok') return;
|
|
422
|
+
$('askHandle').value = '';
|
|
423
|
+
$('askNote').value = '';
|
|
424
|
+
return load();
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
$('askBtn').addEventListener('click', askByHandle);
|
|
428
|
+
$('askHandle').addEventListener('keydown', function (ev) {
|
|
429
|
+
if (ev.key === 'Enter') { ev.preventDefault(); askByHandle(); }
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// ---- the page's states: reading, signed out, inactive, unreadable, signed in ----
|
|
433
|
+
var STATES = ['door', 'inactive', 'readFailed', 'connBody'];
|
|
434
|
+
function show(id) {
|
|
435
|
+
$('loading').hidden = true;
|
|
436
|
+
STATES.forEach(function (s) { $(s).hidden = s !== id; });
|
|
437
|
+
if (id !== 'connBody') $('connMsg').hidden = true;
|
|
438
|
+
}
|
|
439
|
+
function showDoor() { show('door'); }
|
|
440
|
+
bar.ready.then(function (me) {
|
|
441
|
+
if (!me) { showDoor(); return; }
|
|
442
|
+
load();
|
|
443
|
+
});
|
|
444
|
+
})();
|
|
445
|
+
</script>
|
|
446
|
+
<script src="/assets/invite-badge.js"></script>
|
|
447
|
+
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
448
|
+
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
449
|
+
</body>
|
|
450
|
+
</html>
|