#Java

2025-12-14

Kürzlich habe ich einen Artikel gelesen, in dem es um Fragen in einem Vorstellungsgespräch als Java-Entwickler ging. Es wurden einige Fragen vorgestellt und die These aufgestellt, dass die meisten Bewerber diese Fragen nicht beantworten können. Aus diesem Grund möchte ich ...

magicmarcy.de/java-interview-f

#Java-Interview #Grundlagen #JDK #JRE #abstrakte_Klasse #Interface #final #finally #finalise #stack #heap #private #protected #super #Garbage_Collector #GC

Zeitgeisty Aphorismszeitgeisty
2025-12-14

In engine there is more twin than there!.

2025-12-14

Kafka для начинающих: работа с оффсетами на практике

Как работать с оффсетами в Kafka на практике, используя Spring Boot? Разбираем проблемы и их решения на примере интернет-магазина. Простым языком и с примерами кода о режимах коммитов, проблемах с транзакциями и надёжных паттернах.

habr.com/ru/articles/965218/

#apache #java #microservices #kafka

2025-12-14

This piece got me thinking about the early 2010’s;

sanity.io/blog/you-should-neve

The crux being; while you can quickly spin things up with vide coding, you’re not aware of the “secret” complexity or the reasons why current implementations or products are the way they are.

What’s interesting is that we’ve been here before; with NodeJS/Ruby on Rails and Mongo.

Let me explain 🧵
#vibecoding #tech #programming #ai #NodeJS #JavaScript #java #code

2025-12-14

Mon calendrier de l’avent – jour 14

Le projet du jour est une découverte très récente, mais très pratique. Quand on fait de l’architecture as code, on se connecte souvent à des APIs inconnues pour lesquelles il faut écrire un client. Ecrire les appels est assez simple avec Retrofit, mais écrire l’ensemble des classes représentant le modèle est facilement fastidieux. Donc dans mon dernier outillage de documentation d’architecture, j’ai rajouté une partie qui se déclenche quand Jackson me remonte une exception de classe manquante. Et dans ce cas, je génère la classe correspondante.

Et si j’ai longtemps généré des classes a la main (en particulier parce que je faisais ça dans Codingame), je savais qu’il existait des solutions plus performantes. La plus connue actuellement est JavaPoet (dont on se demande ce qu’elle fait dans le repository GitHub de ces ordures de Palantir). Et sa bonne réputation est, je dois l’avouer, bien méritée. Regardez leur exemple, c’est franchement plus lisible que ce que peut produire JavaParser (qui va théoriquement dans le sens inverse).

#java

JAVAPROjavapro
2025-12-14

Python owns research — but is becoming the factory. @ArturSkowronski uncovers the hidden work: FFM APIs, vectorization, ONNX pipelines & GPU offloading. The is quietly turning into an HPC engine. Want to see the evidence?

Dive in: javapro.io/2025/12/11/jvm-iceb

2025-12-14

JSON is convenient, but not exactly efficient.
I wrote a hands-on guide showing how to use Protobuf in Quarkus REST APIs without touching gRPC.

Smaller payloads, faster serialization, real schemas—same REST endpoints.

Read here:
the-main-thread.com/p/protobuf

#Java #Quarkus #Protobuf #REST

Advent Calendar 2025 – From UI Interactions to a Deterministic Refresh Architecture

After the first part explained the conceptual basics and the new interactions among global search, search scopes, and advanced filters, the second part focuses on the technical mechanisms that enable these interactions. It is only the revised refresh architecture – above all the interaction of safeRefresh() and RefreshGuard – that ensures that the OverviewView remains calm, deterministic and predictable despite numerous potential triggers.

So while Part 1 describes what has changed in the user interface and why this structuring was necessary, Part 2 now shows in detail how the internal state machine works, how competing UI events are coordinated and why the View achieves the desired robustness in the first place.

With this foundation, the following chapters can be assigned clearly: they analyse the refresh architecture, the reset mechanism, and the validation and error-handling logic – the technical building blocks that ensure the UI concepts described above not only look good but also function reliably.

The source code for this development step can be found on GitHub

and can be found here: https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-07

Here are some screenshots of the current development state.

Refresh architecture with RefreshGuard

With the introduction of advanced filtering logic, not only does the complexity of the user interface increase, but so does the number of potential conditions that can cause a grid reload. Any change to a filter field, any adjustment to the page size, the opening or closing of the Advanced area, or entering the view itself can trigger a new request to the server. Without additional control, this could trigger a cascade of overlapping refreshes, create unnecessary load, and noticeably degrade the user experience.

Against this background, the refresh architecture of the OverviewView has been specifically revised. The central goal was to reduce the number of possible triggers to a controllable mechanism that exhibits consistent, predictable behaviour across all relevant scenarios. Instead of allowing each UI event to have its own access to the DataProvider, a deliberate hard cut was made: the decision of whether and when a refresh may actually take place is bundled in a dedicated layer. This layer contains the RefreshGuard, which acts as a guardian that determines which processes are allowed to retrieve specific data and which are only allowed to change the internal state.

The technical basis of this control system is surprisingly simple at first. A single flag is introduced at the head of the class that controls whether refreshes are currently allowed:

private boolean suppressRefresh = false;

This flag is evaluated by a small helper method that acts as the only allowed entry point for data updates:

private void safeRefresh() {  logger().info("safeRefresh");  if (!suppressRefresh) {    logger().info("refresh");    dataProvider.refreshAll();  }}

Instead of reloading the grid or DataProvider from multiple locations, the view periodically calls safeRefresh(). The method first logs the refresh attempt, then checks the flag. Only if suppressRefresh is not set, refreshAll() is actually  executed on the DataProvider. This creates a clear separation between the semantic intent to update the content and the operational decision of whether it is allowed in the current context.

Conceptually, the RefreshGuard can be understood as a critical section that allows multiple UI operations to be combined. As long as the code is within such a protected phase, direct refresh calls are suppressed. Only at the end of the section does the guard decide whether an aggregated refresh is required and, if so, execute it in a controlled manner. In the source code, this mechanism is implemented as a small, inner helper class:

class RefreshGuard implements AutoCloseable {  private final boolean prev;  private final boolean refreshAfter;  RefreshGuard(boolean refreshAfter) {    this.prev = suppressRefresh;    this.refreshAfter = refreshAfter;    suppressRefresh = true;  }  @Override  public void close() {    suppressRefresh = prev;    if (refreshAfter) safeRefresh();  }}

The constructor of the guard first stores the current state of suppressRefresh and then sets the flag to true. This means that all safeRefresh() operations called within this block are ineffective; they are logged but do not trigger a reload. When leaving the block (i.e., in the close() method), the previous flag value is restored. Optionally, a final refresh can then be triggered by the refreshAfter parameter. By implementing AutoCloseable, the guard can be used in a try-with-resources block, ensuring that the state is reset correctly even in exceptional cases.

The use of this pattern is particularly evident in the View lifecycle. When attaching the OverviewView to the UI, the column visibilities are first set based on the stored preferences. This process changes the grid state significantly but should not cause new data to be loaded from the server multiple times. Accordingly, the entire block is wrapped in a RefreshGuard :

@Overrideprotected void onAttach(AttachEvent attachEvent) {  super.onAttach(attachEvent);  try (var _ = new RefreshGuard(true)) {    var keys = grid        .getColumns()        .stream()        .map(Grid.Column::getKey)        .filter(Objects::nonNull)        .toList();    var vis = columnVisibilityService.mergeWithDefaults(keys);    grid.getColumns().forEach(c -> {      var k = c.getKey();      if (k != null) c.setVisible(vis.getOrDefault(k, true));    });  }  subscription = StoreEvents.subscribe(_ -> getUI()      .ifPresent(ui -> ui.access(this::safeRefresh)));}

Within the try block, suppressRefresh is set to true; all grid operations run without immediate data retrieval. Only when leaving the block and all column visibilities are set consistently does the guard provide a single final refresh with refreshAfter = true. As a result, the view is loaded exactly once after the first setup, rather than flickering through intermediate states during initialisation.

safeRefresh() also demonstrates its strengths when navigating between pages. The buttons for scrolling forward and backwards only change the internal page cursor and explicitly delegate the data update to the central method:

prevBtn.addClickListener(_ -> {  if (currentPage > 1) {    currentPage--;    refreshPageInfo();    safeRefresh();  }});nextBtn.addClickListener(_ -> {  int size = Optional.ofNullable(pageSize.getValue()).orElse(25);  int maxPage = Math.max(1, (int) Math.ceil((double) totalCount / size));  if (currentPage < maxPage) {    currentPage++;    refreshPageInfo();    safeRefresh();  }});pageSize.addValueChangeListener(e -> {  currentPage = 1;  grid.setPageSize(Optional.ofNullable(e.getValue()).orElse(25));  safeRefresh();});

Here, the use of the guard is deliberately avoided, as each of these actions constitutes a clearly demarcated, independent refresh. Scrolling back or forward to a new page section, or flipping forward, should trigger a change in page size immediately. Nevertheless, all accesses continue to run via safeRefresh(), so that the mechanism remains centrally controllable. If the architecture changes in the future, an adjustment at this point is sufficient to modify the behaviour of the entire view consistently.

Taken together, the combination of safeRefresh() and RefreshGuard transforms the refresh behaviour of the OverviewView from a hard-to-predict byproduct of many UI events to a controlled, deterministic strategy. Complex operations such as initialisation and reset are packed into closed, atomic blocks, while simple actions such as page changes and field changes are explicitly allowed to trigger a refresh. This gives the view both stability and transparency: readers of the code can clearly see where data updates occur, and users of the interface perceive a quiet, responsive application that responds to inputs in a comprehensible way.

Reset Mechanism: Full State-Clear

The reset mechanism of the OverviewView plays a special role within the search and filter architecture. It forms the fastest way back to a clearly defined, neutral initial state – a state in which neither search fragments nor extended filters, sorting options, nor deviating page sizes are active. While earlier implementations often reset only partial aspects, the revised version takes a consistently holistic approach: clicking “Reset” deletes all user-changed parameters without exception and resets the view as if it were being opened for the first time.

Conceptually, this mechanism is directly integrated into the previously introduced refresh architecture. Since the reset involves a large number of individual steps – emptying text fields, resetting checkboxes and date entries, restoring the default sorting, resetting the page size and closing the Advanced area – each of these actions would immediately trigger a refresh when viewed in isolation. To prevent this, and to treat all changes as logical operations, the entire reset process is embedded within a RefreshGuard.

The reset button implementation directly reflects these considerations. The listener for the reset button encapsulates all the necessary steps in a single, clearly defined block:

resetBtn.addClickListener(_ -> {  try (var _ = new RefreshGuard(true)) {    globalSearch.clear();    codePart.clear();    codeCase.clear();    urlPart.clear();    urlCase.clear();    fromDate.clear();    fromTime.clear();    toDate.clear();    toTime.clear();    sortBy.clear();    dir.clear();    pageSize.setValue(25);    sortBy.setValue("createdAt");    dir.setValue("desc");    currentPage = 1;    searchScope.setValue("URL");    advanced.setOpened(false);    setSimpleSearchEnabled(true);    globalSearch.focus();  }});

The reset process begins by clamping a RefreshGuard with the parameter set to true. This first sets the suppressRefresh flag, so that all safeRefresh() calls indirectly triggered in this block ‑remain ineffective. Only when leaving the block (controlled by refreshAfter = true) is a final refresh executed, making the cumulative new state visible in the grid.

Within the block, all user inputs are systematically returned to their original state. First, all search and filter fields are cleared: the global search field, the shortcode and URL fragments in the Advanced area, and the case-sensitivity checkboxes. The date and time fields for the time window under consideration are then set to zero. This ensures that no old period inadvertently affects later requests.

In the next step, the sorting is reset to its default values. First, sortBy and dir are removed to avoid potential inconsistencies; then, createdAt is explicitly set as the sort field and desc as the sort direction. The page size is also deliberately set back to the default value of 25 entries per page, and the page cursor currentPage is set to one. This creates a state that corresponds to the first time you enter the view: no running filters, a defined sorting and a comprehensible page setting.

The global search logic is also reinitialised. The search scope is reset to URL, and the Advanced scope is closed by Advanced.setOpened(false ). Calling setSimpleSearchEnabled(true) re-enables simple mode, and globalSearch.focus() ensures that the cursor lands directly in the global search field. This results in an intuitive process for the user: After the reset, he sees a neutral overview and can immediately start a new, simple search.

This keeps the user interface completely quiet during the reset: no flickering, no multiple queries, no inconsistent intermediate layout. Only when the entire process is complete is a single, final refresh executed, which establishes a consistent initial state across the grid. This stability is not only crucial for the user experience but also facilitates extensibility, as additional reset steps can be added without introducing new side effects. As long as new fields are included in this guard block, they remain part of the same atomic operation.

In combination with the search and filter logic, this results in a reset mechanism that is both semantically and technically cleanly modelled. Semantic because the user has a clear expectation – “everything back to square one” – that is fully met. Technically, because the mechanism is embedded in the central refresh architecture by the RefreshGuard, it causes neither uncontrolled side effects nor hidden data retrievals. On this basis, subsequent chapters can now address error cases, validations, and logging in a more granular way without touching the basic reset path again.

Error handling, validation, and robustness

With the growing number of functions in OverviewView, robustness is increasingly critical. The user interface should not only work reliably in ideal scenarios but also handle incomplete, contradictory, or incorrect inputs. The combination of global search, extended filter area, time windows, sorting settings and page size in particular presents the system with the challenge of recognising and stably handling even complex and potentially conflict-prone states.

In the revised architecture, robustness is not treated as an afterthought, but as an integral part of the UI logic. Many validation and error-avoidance mechanisms are deeply embedded in interaction points: in ValueChange listeners, when switching between Simple and Advanced modes, and when resetting and deriving a binding search string. The view does not aim to take away all user freedom, but instead offers a controlled environment in which only consistent states can arise. The technical side deliberately avoids complex error messages in favour of clear, deterministic rules that become directly visible in the interaction of the input elements.

A central element of this robust logic is the automatic verification of consistency for the search and filter fields. The pattern of defensive synchronisation is already clearly evident in the global search field:

globalSearch.addValueChangeListener(e -> {  var v = Optional.ofNullable(e.getValue()).orElse("");  if (searchScope.getValue().equals("Shortcode")) {    codePart.setValue(v);    urlPart.clear();  } else {    urlPart.setValue(v);    codePart.clear();  }});

This logic ensures that the shortcode and URL fragment cannot be active simultaneously. As soon as the user enters something in the global search box, the value is interpreted as either a shortcode or a URL. The other field is consistently emptied. In this way, there are no conflicting filter states that would force the application to fulfil two search intentions simultaneously.

The Scope Selection listener reinforces this rule by ensuring that even subsequent changes to the search scope always result in a consistent state:

searchScope.addValueChangeListener(_ -> {  var v = Optional.ofNullable(globalSearch.getValue()).orElse("");  if ("Shortcode".equals(searchScope.getValue())) {    codePart.setValue(v);    urlPart.clear();  } else {    urlPart.setValue(v);    codePart.clear();  }});

This prevents a user from typing a search query in URL mode, then switching to shortcode, thereby implicitly creating an invalid search model. The UI detects this state early and maps it to a clear, comprehensible model.

The validation and cleanup mechanisms are particularly evident in advanced mode when deriving a valid simple search state. The applyAdvancedToSimpleAndReset() method  is the technical condensation of this approach:

private void applyAdvancedToSimpleAndReset() {  String code = Optional.ofNullable(codePart.getValue()).orElse("").trim();  String url = Optional.ofNullable(urlPart.getValue()).orElse("").trim();  final boolean hasCode = !code.isBlank();  final boolean hasUrl = !url.isBlank();  final String winnerValue = hasCode ? code : (hasUrl ? url : "");  final String winnerScope = hasCode ? "Shortcode" : "URL";  try (var _ = new RefreshGuard(true)) {    codePart.clear();    codeCase.clear();    urlPart.clear();    urlCase.clear();    fromDate.clear();    fromTime.clear();    toDate.clear();    toTime.clear();    sortBy.clear();    dir.clear();    sortBy.setValue("createdAt");    dir.setValue("desc");    searchScope.setValue(winnerScope);    if (!winnerValue.isBlank()) {      globalSearch.setValue(winnerValue);    } else {      globalSearch.clear();    }    setSimpleSearchEnabled(true);    globalSearch.focus();  }}

Several basic principles of robustness are intertwined here. First, it checks whether a shortcode or a URL fragment is set. If both are present, the shortcode fragment is given priority – a clear and comprehensible rule that avoids ambiguity. The entire Advanced area is then consistently cleaned up to ensure that no old or partially set values are unintentionally included in future filters.

In addition, RefreshGuard plays a special role in robust processes. Without the guard, the numerous changes within this method would trigger a series of refresh events. However, the guard suppresses these events selectively and triggers exactly one consistent refresh at the end. This prevents flickering UI transitions and ensures the user always sees the final state.

Another important component is validating time windows. The combination of DatePicker and TimePicker can naturally generate incomplete entries – such as a set date without time or vice versa. The logic in the backend transport takes care of these cases, but already in the UI code, a defensive determination of the timestamp prevents potential errors:

private Optional<Instant> combineDateTime(DatePicker date, TimePicker time) {  var d = date.getValue();  var t = time.getValue();  if (d == null && t == null) return Optional.empty();  if (d == null) return Optional.empty();  var lt = (t != null) ? t : LocalTime.MIDNIGHT;  return Optional.of(lt.atDate(d).atZone(ZoneId.systemDefault()).toInstant());}

The method is generous, but clear: a time value without a date is not a valid filter. In case of doubt, times are set to midnight, which keeps the model stable even in incomplete scenarios. This type of defensive modelling prevents incomplete UI inputs from leading to inconsistent backend requests.

The event handlers provide additional technical protection for paging and navigation. Actions such as scrolling between pages or changing the page size have a direct effect on the database, but should not trigger any unexpected side effects. The consistent use of safeRefresh() ensures that these changes only take effect if the refresh context allows it:

pageSize.addValueChangeListener(e -> {  currentPage = 1;  grid.setPageSize(Optional.ofNullable(e.getValue()).orElse(25));  safeRefresh();});

Here, too, robustness is created by clear rules: A new page size resets the page cursor and triggers a controlled reload – never several, never via a detour.

Finally, logging also contributes significantly to diagnostic robustness. In many places in the code, logger().info() is deliberately used to signal when search changes, refresh processes, or state transitions occur. These traces enable precise reconstruction of complex error patterns in UI-backend interactions without requiring additional debugging mechanisms.

The result is a system that does not rely on errors that are subsequently intercepted, but on deliberately modelled, conflict-free states. The user guidance is designed to prevent invalid or contradictory situations, and the technical foundation ensures that even incomplete or ambiguous inputs are converted into stable, controlled processes. Thus, the combination of validation, synchronisation, and protection mechanisms provides a viable basis for further expansion of the application.

Conclusion and outlook

The revision to the OverviewView is more than a collection of individual improvements. It marks a structural change that fundamentally reshapes the interaction among search, filtering, data updates, and user interaction. From an initially heterogeneous interface with scattered responsibilities, a clearly modelled, consistent and technically robust view has emerged, whose behaviour is comprehensible and extensible in all essential dimensions.

A key result of this revision is the standardisation of the search logic. The introduction of a global search box, together with scope selection, forms a small, self-contained state machine that provides an intuitive entry point for the user. Complemented by the extended filter area, a flexible system is created that supports both fast search queries and more complex filter combinations – without competing with each other. The clear switch between advanced and straightforward mode prevents contradictory states and keeps cognitive effort low.

Equally important is the redesigned refresh architecture. With safeRefresh() and the RefreshGuard, a mechanism has been introduced to stabilise the application’s refresh behaviour. Complex operations such as initialisation or reset are bundled into atomic, deterministic processes, while simple interactions can still react directly. This pattern operates in the background and is particularly noticeable to the user when the operation is quieter and less erratic.

The grid itself has also been further developed in terms of functionality and ergonomics. Copy functions, context-sensitive opening of details, dynamic flow badges and an improved column layout transform the table into an interactive workspace that not only provides information, but also enables action. This proximity between data and interaction reduces the need for additional dialogue changes, thereby contributing to a more fluid workflow.

The robustness of the view ultimately results from a variety of small but effective mechanisms: automatic synchronisation of filter fields, defensive evaluation of incomplete entries, clear prioritisation rules, and consistent logging. All these aspects ensure that the application remains reliable even under unusual input combinations and that the causes of errors can be traced if necessary.

This structural basis enables broad future expansion. The clear separation of UI logic, filter model, and refresh strategy provides a stable foundation on which subsequent features can be built without breaking points. Among other things, the following are conceivable:

– a server-side full-text search that extends the Simple/Advanced model, – colour or iconographic markings of other states such as “soon to expire”, – bulk actions for multiple selections, – a modular sorting and filtering pipeline, – tagging or labelling functions for short URLs, – advanced column settings or custom views.

The new OverviewView thus not only represents an improvement over the status quo but also marks a structural turning point. It creates clarity where implicit or scattered logic previously prevailed and establishes mechanisms that keep the system stable over the long term. In its entirety, the revision represents an important step towards a modern, scalable, and maintainable UI architecture that meets the requirements of growing use cases.

Cheers Sven

#Advent2025 #EclipseStore #Java #Vaadin

Screenshot of the URL Shortener overview, featuring search bar, filter options, and a table listing shortcodes, URLs, created dates, expiry information, and action buttons.Screenshot of the URL Shortener Overview interface, displaying search filters, pagination options, and a table listing shortened URLs along with their details.
Juan C Nunojuancnuno
2025-12-14
lmorchard's linkslinks@lmorchard.com
2025-12-14

Polyglot AI Agents: WebAssembly Meets the JVM

"In previous posts, Davide Eynard demonstrated how to run agentic frameworks in the browser, while Baris Guler showed how to extend them with in-browser inference and support for multiple programming languages. In this post, we leverage the JVM's polyglot capabilities to create a self-contained, enterprise-optimized server-side blueprint that combines the performance benefits of WebAssembly with the reliability and maturity of Java's ecosystem."

https://blog.mozilla.ai/polyglot-ai-agents-webassembly-meets-the-java-virtual-machine-jvm/?utm_source=Social%20(RB)

#ai #java #jvm #llm #wasm

N-gated Hacker Newsngate
2025-12-14

🤔 So, you want to play with and this mysterious Fil-C? 🎩✨ Congratulations, you've discovered that and sandboxing are as related as cats and algebra! 🐱➕📚 You could build a "safe" program that obliterates your files, but at least it won't have memory leaks. Bravo! 🏆🚀
fil-c.org/seccomp -C

KaizenCoreKaizenCore
2025-12-13

👋 Hello!

KaizenCore here - we build open-source Minecraft server plugins and tools.

What we make:
🚀 Kaizen-Launcher - Custom optimized MC launcher
🏠 Gameplay plugins - Homes, Portals, Chat, etc.

We're migrating from Twitter to join the FOSS-friendly community here on fosstodon. Excited to share development updates, learn from this amazing community, and build together!

🔗 github.com/KaizenCore
💬 discord.gg/JbGbTxD6Kt
🚀 launcher.kaizencore.tech

Anthony Acciolyanthony@accioly.social
2025-12-13

RE: mastodon.social/@TheDonRaab/11

If you haven't done so, go grab your copy. The book goes beyond Eclipse Collections, offering great ideas on how to categorise functions and documentation, how to build fluent, intuitive, testable, and easy-to-evolve APIs in the open and much more. It's filled with decades of knowledge absorbed by the author, from the early days of OOP and Smalltalk to modern, expressive functional/OO hybrid Java that would surprise even the most sceptical programmer.

#EclipseCollections #Java #Free #Book

2025-12-13

🕹️ Title: UnCiv
🦊️ What's: A libre TB strategy game & empire building inspired by Civ V
🏡️ -
🐣️ github.com/yairm210/UnCiv
🔖 #LinuxGaming #Flagship #TBS #GrandStrategy
📦️ #Libre #Java #Bin #Arch #Flatpak
📕️ lebottinlinux.vps.a-lec.org/LO

🥁️ Update: 4.19.0 p1/2 / + 4.18.19
⚗️ Signific. vers. 🦍️
📌️ Changes: github.com/yairm210/Unciv/rele
🦣️ From: mastodon.social/@holarse/11571

🏝️ youtube.com/embed/u28tWIsC01E
🩳️ youtube.com/embed/w7mhrg4fpG0
🎲️ youtube.com/embed/O_qNLmeyqWE
🎲️[fr] youtube.com/embed/tLNWW0BHZ04

🕶️ Its UI with a strategy game. The central part is occupied by a map divided into hexagons with pixelart units. The period is medieval, several Mongol units & a trebuchet surround a castle in the center. The units have an icon for easy understanding (mongol, sword, trebuchet, ..), slightly transparent when not selected. The Mongol on horseback on the right of the castle is selected. The castle (named Elephantine) seems to be under siege. On the left of the UI several command buttons (Sleep, Pillage Pasture, Explore, Wait, ..), below the characteristics of the selected unit (Keshik, 2 stars, XP 5/20, ..), in the lower middle a box with what seems to be a report on the selected unit (Keshik vs Elephantine). To the right of the UI, a message area ("The resistance in Sydney has ended!", ..), lower right a mini map. At the top of the interface a hamburger menu, and near it the title "Mongolia". At the top middle several indicators, the period (1520 AD) and on the right an "Overview" button.

📚️ UnCiv is a libre, multi-platform, single-player / multi-player (by exchange of "User ID"), turn-based strategy game (4X) & Empire building, inspired by Civilization V: the game starts in 4000 BC Christ, the player directs a nation or an ethnic group, with the aim of supremacy over the other protagonists, leading his group until 2050 AD. It's a neat game that offers in-game help to get started, solo/multiplayer, map editor and game save / load. Very good!
Curated Hacker NewsCuratedHackerNews
2025-12-13

Show HN: I audited 500 K8s pods. Java wastes ~48% RAM, Go ~18%

github.com/WozzHQ/wozz

Client Info

Server: https://mastodon.social
Version: 2025.07
Repository: https://github.com/cyevgeniy/lmst