Files
devplacepy/devplacepy/static/js/Application.js
T
blindxfishandClaude Fable 5 80956ce0f4 Add Opinion Wars: week-long two-faction battles attached to posts
A new post attachment type beside polls: the composer gains a Start
Opinion War builder (same disabled-inputs opt-in as the poll builder)
that names exactly two factions; the battle runs for exactly 7 days
from post creation. Members join a side, may defect at any time
(damage already dealt stays with the faction it was dealt to), and
fight once per 24 hours per battle. A fight spends 25 Code Farm coins
and deals deterministic level-weighted damage: 100 + 10 * min(level,
20) HP, so a newcomer deals 110 and a veteran caps at 300 - no
randomness anywhere.

The battle renders on the post card as a CSS pixel-art battlefield
(box-shadow sprites: castles, faction flags, marching soldiers, a
flickering campfire; steps() animation, disabled under reduced motion)
with live HP bars, a countdown, the viewer's faction strip, top
contributors and an event ticker. Live frames ride pub/sub on
public.battle.{uid} via a relay on the service-lock owner, with the
durable opinion_war_events trail (per-war atomic seq) as the source of
truth and a 15s incremental poller as fallback. /battles lists battles
with active/ended/mine filters, search and pagination.

Every mutation is a conditional UPDATE via conditional_update_row: the
fight sequence claims the cooldown first, then spends coins, then lands
the damage, compensating earlier steps on any later refusal so a crash
costs a turn, never coins. Resolution is lazy on read (no cron):
an exactly-once CAS computes the winner in the statement, awards XP
(participation, winner bonus, top damage dealer bonus; draws pay
participation only), emits the result event and notifies fighters. The
OpinionWarService backstop resolves unviewed wars and sends
fight-ready notifications, exactly-once via a marker CAS.

Fan-out: battle notification type, four badges, audit keys
(battle.create/join/switch/fight/resolve), Devii actions (join/fight
confirm-gated), API docs group, docs prose page, sitemap and topnav
entries, REPORTABLE_TARGETS registration, post-delete cascades,
README and nested CLAUDE.md documentation.

Verified with the four-layer procedure: property checks over the full
damage domain, 1200-step stateful fuzz (hp-sum invariant, coins never
negative, resolved totals frozen), and real 8-process races proving
exactly-once semantics for concurrent fights, double-spends across two
wars, resolution XP and double-joins. Persisted tests in
tests/unit/services/opinionwar, tests/api/battles, tests/e2e/battles
and tests/api/posts/create.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 23:30:02 +02:00

127 lines
5.8 KiB
JavaScript

// retoor <retoor@molodetz.nl>
import "./components/index.js";
import { ModalManager } from "./ModalManager.js";
import { FormManager } from "./FormManager.js";
import { VoteManager } from "./VoteManager.js";
import { NotificationManager } from "./NotificationManager.js";
import { ProfileEditor } from "./ProfileEditor.js";
import { MobileNav } from "./MobileNav.js";
import { CommentManager } from "./CommentManager.js";
import { ContentEnhancer } from "./ContentEnhancer.js";
import { DomUtils } from "./DomUtils.js";
import { PushManager } from "./PushManager.js";
import { CounterManager } from "./CounterManager.js";
import { ReactionBar } from "./ReactionBar.js";
import { BookmarkManager } from "./BookmarkManager.js";
import { PollManager } from "./PollManager.js";
import { WarComposer } from "./WarComposer.js";
import { ApiKeyManager } from "./ApiKeyManager.js";
import { AvatarRegenerator } from "./AvatarRegenerator.js";
import { CustomizationToggle } from "./CustomizationToggle.js";
import { NotificationPrefs } from "./NotificationPrefs.js";
import { AiCorrection } from "./AiCorrection.js";
import { AiModifier } from "./AiModifier.js";
import { InteractionsPref } from "./InteractionsPref.js";
import { TelegramPairing } from "./TelegramPairing.js";
import { AdminNotificationDefaults } from "./AdminNotificationDefaults.js";
import { UserAiUsage } from "./UserAiUsage.js";
import { Tabs } from "./Tabs.js";
import { DeviiTerminal } from "./DeviiTerminal.js";
import { ZipDownloader } from "./ZipDownloader.js";
import { ProjectForker } from "./ProjectForker.js";
import { IssueReporter } from "./IssueReporter.js";
import { IssueAttachments } from "./IssueAttachments.js";
import { PlanningGenerator } from "./PlanningGenerator.js";
import { MediaGallery } from "./MediaGallery.js";
import { ReportDialog } from "./ReportDialog.js";
import { TermsGate } from "./TermsGate.js";
import WindowManager from "./components/WindowManager.js";
import { ContainerTerminalManager } from "./ContainerTerminalManager.js";
import { PubSubClient } from "./PubSubClient.js";
import { LiveNotifications } from "./LiveNotifications.js";
import { PresenceManager } from "./PresenceManager.js";
import { OnlineUsers } from "./OnlineUsers.js";
import { LocalTime } from "./LocalTime.js";
import { EditorLauncher } from "./EditorLauncher.js";
import { ScrollMemory } from "./ScrollMemory.js";
import { GameFarm } from "./GameFarm.js";
import { Accessibility } from "./Accessibility.js";
import { OverflowTabs } from "./OverflowTabs.js";
import { AiAutoload } from "./autoload/AiAutoload.js";
import "./components/AiInteraction.js";
import "./components/AiStatus.js";
import "./components/AiActions.js";
import "./components/AiHelp.js";
import "./components/AiOption.js";
import "./components/AiConfirm.js";
import "./components/AiChoice.js";
import "./components/AiChoiceMulti.js";
import "./components/AiField.js";
import "./components/AiSelect.js";
import "./components/AiGroup.js";
import "./components/AiMarkdownFallback.js";
class Application {
constructor() {
this.accessibility = new Accessibility();
this.aiAutoload = new AiAutoload(document);
this.aiAutoload.start();
this.dialog = document.createElement("dp-dialog");
this.contextMenu = document.createElement("dp-context-menu");
this.toast = document.createElement("dp-toast");
this.lightbox = document.createElement("dp-lightbox");
document.body.append(this.dialog, this.contextMenu, this.toast, this.lightbox);
this.termsGate = new TermsGate(this.dialog);
this.modals = new ModalManager();
this.forms = new FormManager();
this.votes = new VoteManager();
this.notifications = new NotificationManager();
this.profile = new ProfileEditor();
this.mobileNav = new MobileNav();
this.comments = new CommentManager();
this.content = new ContentEnhancer();
this.dom = new DomUtils();
this.push = new PushManager();
this.pubsub = new PubSubClient();
this.counters = new CounterManager(this.pubsub);
this.reactions = new ReactionBar();
this.bookmarks = new BookmarkManager();
this.polls = new PollManager();
this.warComposer = new WarComposer();
this.apiKey = new ApiKeyManager();
this.avatarRegenerator = new AvatarRegenerator();
this.customizationToggle = new CustomizationToggle();
this.notificationPrefs = new NotificationPrefs(this.pubsub);
this.aiCorrection = new AiCorrection();
this.aiModifier = new AiModifier();
this.interactionsPref = new InteractionsPref();
this.telegramPairing = new TelegramPairing(this.pubsub);
this.adminNotificationDefaults = new AdminNotificationDefaults();
this.userAiUsage = new UserAiUsage();
this.tabs = new Tabs();
this.windows = new WindowManager();
this.containerTerminals = new ContainerTerminalManager();
this.devii = new DeviiTerminal();
this.zipDownloader = new ZipDownloader();
this.projectForker = new ProjectForker();
this.issueReporter = new IssueReporter();
this.issueAttachments = new IssueAttachments();
this.planningGenerator = new PlanningGenerator();
this.mediaGallery = new MediaGallery();
this.reportDialog = new ReportDialog();
this.liveNotifications = new LiveNotifications(this.pubsub, this.toast);
this.presence = new PresenceManager(this.pubsub);
this.onlineUsers = new OnlineUsers(this.pubsub);
this.localTime = new LocalTime();
this.scrollMemory = new ScrollMemory();
this.editorLauncher = new EditorLauncher();
this.gameFarm = new GameFarm();
this.overflowTabs = new OverflowTabs();
}
}
const app = new Application();
window.app = app;
app.containerTerminals.restore();