Foundation — upgrade notes for host databases
Written 2026-08-13 for the host developer, not the Foundation developer. The companion to README.md: that document says how a release is cut, this one says what a host must do to accept it.
One section per release, newest first. Every entry answers three things — what changed, what the host sees, what the host must do. An entry that needs no action still belongs here, because "we checked and it is safe" is worth recording once rather than re-deriving per site.
What counts as a breaking change
Foundation's contract with a host is every method carrying "shared":true, at the signature it had when the host was written. Not the methods a header comment calls public — hosts invoke Foundation by name at runtime through EXECUTE METHOD, stored method names and object-method wiring, and none of that is visible to any static reading of the component.
That distinction is not theoretical. In March 2026 the surface was narrowed from 697 to 451 shared methods on the strength of Access: header comments, shipped as Foundation 20 LTS, and broke host databases in the field. The rule since: a shared method is contract until runtime evidence from real hosts says otherwise, and removal is staged deprecation, never a flag flip.
So the categories below are ordered by what they cost a host:
- Breaking — a call that compiles today stops working. The host must act.
- Behavioural — the call still works and returns a different answer. The host must decide whether it cares.
- Additive — new capability. Nothing to do.
6.0.0-beta.2 (private beta, 8 November 2026)
Breaking
None. No shared method was removed or made private, and every call that compiled against beta.1 still compiles.
Behavioural
The Window menu lists windows, not processes (FND-54). Foundation now keeps one registry of windows, each entry holding the window reference and its process. A process with several windows gets one Window-menu item per window that asks for one, and choosing an item raises that window rather than every window of the process.
Fnd_Menu_Window_Add works on the current form window, so call it after the window is open. It also decides which window is the process's prime: the one whose closing lets the process method carry on, and which Close All closes last. Left out, a window added from process-method level is taken to be prime and one added from inside a running form is not. Outside a form it falls back to the process's frontmost window. What to do: nothing for the usual On Load call. Pass False as the new second parameter for a window opened with DIALOG(...; *) from process-method level, because the process carries on after it; "" as the first parameter keeps the window's title.
Fnd_Menu_Window_Remove takes a window reference or a process number. A number that is a registered window removes that window; any other number removes all of that process's windows. The window stays open, and stays in Close All. With no parameter it removes the current process's most recently added window.
Close All reaches every closeable window of a process, not only its frontmost window. Each window is asked once, its siblings before the process's prime window, which is asked only when they have gone. A form that handles On Outside Call still decides for itself, with Fnd_Wnd_CloseNow and Fnd_Wnd_CancelCloseAll; Foundation brings that window to the front first, because the outside call reaches only the frontmost one. What to check: a closeable window whose form neither handles On Outside Call nor has an onClose method used to ignore Close All, and is now closed with no chance to refuse.
Fnd_Wnd_SendCloseRequests marks a window, not a process. Called in a process with no window yet, as at the top of a process method, it marks the next window that process opens through Fnd_Wnd_OpenWindow or Fnd_Wnd_OpenFormWindow, and only that one. Otherwise it marks the frontmost visible window. Fnd_Wnd_SendCloseRequests(process) now works for another process, provided it has a visible window: it used to record the caller's process name, so the entry was dropped as dead. A host that passes a number, and was relying on nothing happening, should check that call.
Fnd_Wnd_MoveOnScreen reads the screens each time, so rearranging monitors while the application runs is noticed, and its multi-screen handling now runs in Windows SDI mode.
Fnd_Out_AddToSelection no longer raises error 1600 when called from outside the list's table context; it does nothing when there is no record to add.
Fnd_Date_DateAndTimeToISO is correct for early dates on 4D 21 R. On 21 R4 and 21 R5, 4D's String(date; ISO date) returns 0000-00-00T00:00:00 for every date up to and including 1 January 1900, and this method used to pass that on. It now builds the text itself. Its output on every other date and version is unchanged. What to do: nothing for Foundation's method; code of your own that calls String with ISO date on such dates has the same fault on 21 R.
Additive
- The Fnd_Out search box (FND-81) is new and optional. To use it, add a subform object named exactly
Fnd_Out_Search, with detail formFnd_Out_Search, to the host'sFnd_Outform once, and declare each table's searchable fields inFnd_Hook_Out_SearchFieldswithFnd_Out_AddSearchField. Foundation generates both hooks (Fnd_Hook_Out_SearchFieldsand the optionalFnd_Hook_Out_Search) the first timeFnd_Outruns. A table with no declared fields hides the box; a host without the object is unaffected. Draw the object anywhere in the toolbar band, about 192 × 26: Foundation places it when the list opens and on every resize, 10 pixels from the window's right edge, centred in the band and never over the toolbar buttons, and hides it when the window is too narrow to fit it. The empty box shows the searched fields' names in grey, and its tip lists them all. A name is the field's virtual field name unlessFnd_Out_AddSearchField's optional third parameter (orfo_titlein the object form, besidefo_fieldandfo_operator) gives another.Fnd_Out_SearchHint(hint; tooltip), called in the hook, replaces the hint or the tip for that table, andFnd_Out_SearchHelpreturns the generated list for a tip of your own. - Fonts in the list window (FND-82). Users can choose the list's font in View Options and a single column's font in Advanced; both are saved with the list's layout. A saved layout holds a font only when the user chose one, so
Fnd_Out_Default(Fnd_Out_FontName; …)still sets the font everywhere else. - The window registry is published (FND-54):
Fnd_Wnd_Register,Fnd_Wnd_Unregister,Fnd_Wnd_WindowsandFnd_Wnd_CloseWindow. Windows opened byFnd_Wnd_OpenWindoware registered automatically and stay out of the Window menu and Close All until they opt in. A window opened with 4D's ownOpen window, or a palette fromFnd_Wnd_OpenFormWindow, is not registered until you register it. - The About box credits the incorporated components and further contributors.
6.0.0-beta.1 (private beta, 1 November 2026)
First release under the new versioning scheme. The component is built for four targets from one commit, all carrying the same version: 20L (4D 20 LTS), 20R (4D 20 R), 21L (4D 21 LTS) and 21R (4D 21 R). The floor is 4D 20 LTS; 4D 19 is no longer supported. Foundation is released under the MIT licence.
Breaking
Foundation's own licensing is gone. Fnd_Gen_Reg_*, Fnd_Gen_RemoveActivationCode, their two forms and Resources/Fnd_License.xml are removed (FND-61, FND-62). A host that called Fnd_Gen_RemoveActivationCode, or had a menu item for it, must remove the call. Fnd_Reg, which licenses the host's application, is unaffected.
cs.fnd.wnd is no longer published. The unfinished class is now _wnd_sketch. A host that referred to cs.fnd.wnd must remove the reference; nothing worked through it (FND-44).
The standalone Logging component is removed, and Foundation records through Fnd_Log (FND-96). A host that installed Logging.4dbase only for Foundation's sake can remove it; a host that calls the Logging component's own methods keeps its copy.
German's language code is DE, not GR. Fnd_Loc_LanguageCode returns DE. A host with its own _GR lists for German renames them _DE.
Behavioural
Fnd_Reg offers to create [Fnd_Reg]. A host that uses Fnd_Reg without a [Fnd_Reg] table no longer aborts. In an interpreted host during development, Fnd_Reg asks whether to create the table, then asks for a restart; a compiled host gets a report. Either way Fnd_Reg stays inactive for the session. Hosts with the table already in place need do nothing (FND-85). Fnd_Reg_Init is not part of the published API: do not call it from a host.
Fnd_Reg registration fixes (FND-87, FND-90, FND-93, FND-94). Duplicate records are resolved rather than aborting; days remaining honour a demo period set before the demo starts; the registration dialog is not shown once registered; and a tampered record reports Expired on the call that detects it. Hosts need do nothing.
Fnd_Loc names a missing Fnd_Host_ListToArray. A host without the hook now gets one report naming it, instead of empty strings (FND-39). Hosts that have the hook see no change.
Fnd_Dlg keeps its state in objects. The module's process and interprocess variables (Fnd_Dlg_Text1_t, <>Fnd_Dlg_ProgFrom_i and the rest) are gone, replaced by Fnd.Dlg and Storage.Fnd.Dlg. Component variables were never visible to a host, so the public methods behave as before, with one exception: Fnd_Dlg_SetIcon(0), or any value other than Fnd_Dlg_WarnIcon and Fnd_Dlg_StopIcon, now resets the next alert to the Note icon instead of leaving the previous alert's icon in place. A host that wants the old leak has none.
Development history in method headers is one line. Every modification line dated 2026-08-01 to 2026-11-01 reads "Prepare Foundation 6 release"; earlier lines are unchanged. Nothing to do; it is why a diff against Foundation 5 headers looks shorter than the code change.
Additive
Fnd_Reg_CreateTableis new and shared, for creating the[Fnd_Reg]table by hand in an interpreted host.Fnd_Art_VersionHistoryis new. A host can pass release notes (styled text, newest first) to show on the About box's About tab; without them the area is hidden. The generic About box is now tabbed (About / Foundation / Foundation Licence), resizable, and credits Dave Batton and Walt Nelson (FND-59).Fnd_VS_zProbeHostListsis published, as a diagnostic: it reports which route to a host's choice lists works in that database.
Also in 6.0.0-beta.1 — the August 2026 modernisation of Fnd_Text and Fnd_Data
The Fnd_Text and Fnd_Data modules were modernised: hand-written MD5 and Base64 replaced by the 4D commands that now do the same work, and module state moved out of interprocess variables into Storage.
Breaking
63 methods are no longer callable from a host. They carried "shared":true and no longer do — 33 Compiler_Fnd_* methods and 30 Fnd_*_Init methods, including Fnd_Init itself. The shared surface went from 698 to 633.
This is deliberate and settled (Wayne, 2026-08-13). Neither family is part of the published API: Compiler_Fnd_* methods are declaration scaffolding a host has no reason to name, and Fnd_*_Init is called by every public method in its module on entry — and has been since 2004 — so a host calling one was calling it a second time for no effect.
Note that the change is not merely documentary. The %attributes flag is what 4D checks when a host resolves a name, so the same edit that corrected the claim also withdrew the method.
A host that calls any Fnd_*_Init directly will therefore fail at that call. This is not hypothetical: Foundation-Product-Sales, the reference host, calls Fnd_Wnd_Init from a form object method (Forms/_Matrix_Testing/ObjectMethods/Button.4dm, line 1).
What to do. Nothing, if the host only calls public Fnd_<Module>_<Verb> methods — every one of those kept its flag, verified method by method. If the host calls an Init or a Compiler_Fnd_* method directly, delete the call rather than replacing it; there is nothing to replace it with, and nothing was relying on it.
Three MD5 helpers were removed, having been shared:true: Fnd_Text_TextToMD5_Bits32Add, Fnd_Text_TextToMD5_Bits32Left, Fnd_Text_TextToMD5_Calc. They were the internals of the hand-written digest and are meaningless outside it. A host calling them was reaching into the implementation; use Fnd_Text_TextToMD5.
Interprocess and process variables are gone, not renamed. A host reading any of these by name finds nothing:
| Was | Now |
|---|---|
<>Fnd_Data_Initialized_b | Storage.Fnd.Data.Initialized |
<>Fnd_Data_NameTitles_at | Storage.Fnd.Data.nameTitles (collection) |
<>Fnd_Data_UserBypassKey_i | Storage.Fnd.Data.bypassKey |
Fnd_Data_FormatError_i | Fnd.Data.formatError (per-process object) |
<>Fnd_Text_Initialized_b | Storage.Fnd.Text.Initialized |
<>Fnd_Text_CapExcludeWords_at | Storage.Fnd.Text.capExcludeWords (collection) |
<>Fnd_Text_Text2MD5_SINUS_ai | removed — the sine table of the digest it served |
Fnd_Text_TempBlob_blob | removed — the Base64 methods use a local blob |
Prefer the accessor to the storage location in every case: Fnd_Data_FormatError rather than reading Fnd.Data.formatError, Fnd_Text_CapitalizeExclude rather than touching the collection. The accessors are shared and stable; the layout behind them is not contract.
Two flags were re-spelled to the US spelling the rest of the component uses. <>Fnd_Tbl_Initialised_b is now <>Fnd_Tbl_Initialized_b, and the per-process Fnd.List.Initialised is now Fnd.List.Initialized. This matters beyond tidiness: Fnd_Gen_Reset builds the flag name as a formula and swallows the error when it misses, so a module spelling its own flag the British way was silently never reset.
Behavioural
Fnd_Data_FormatWebURL now defaults to https. A URL arriving with no scheme was given http://; it is now given https://. Trailing-slash normalisation, which previously matched only http://, now applies to both. A URL that already carries a scheme is untouched, as before.
Fnd_Data_FormatWebURL("example.com") // https://example.com/
Fnd_Data_FormatWebURL("example.com"; {scheme: "http"}) // http://example.com/
Stored values are not migrated. A host that formatted URLs on entry has http:// in its data and will produce https:// from now on, so the same input will not match a stored value. Pass {scheme: "http"} to keep the old behaviour at a particular call site.
Fnd_Text_EncodeBase64Blob no longer prefixes a carriage return. The old encoder wrote a line break before the first character and sized the blob to allow for it, so Fnd_Text_TextToBase64("Man") returned five characters where four were asked for. It now returns TWFu.
This reaches Fnd_Text_TextToBase64 too, and it changes the output every existing caller has been receiving. Anything that stored, transmitted or compared those values holds strings with a leading carriage return that fresh encodings will no longer match. The 76-character wrapping is unchanged — BASE64 ENCODE does not wrap, so the wrapper still does.
Verified unchanged
Recorded because they are the changes most likely to be feared:
- MD5 digests are identical.
Fnd_Text_TextToMD5delegates toGenerate digest, and the default charset stays ISO-8859-1 — what the hand-written digest always used — so stored digests still match.Test_Fnd_Textasserts the RFC 1321 vectors and the accented-text digest against the default. - Base64 decoding still tolerates whitespace. The old decoder ignored line breaks incidentally;
BASE64 DECODEis stricter, soFnd_Text_DecodeBase64Blobnow strips CR, LF, tab and space before calling it. Same result, deliberately. Fnd_Text_Wrapoutput is unchanged. Rewritten to walk an index rather than delete the front of the text on each pass — a performance change only, pinned by boundary tests including exactly-at-limit, over-limit with no space, and an embedded carriage return.
Additive
cs.fnd.text— theFnd_Textmodule through the class store:cs.fnd.text.new().capitalize("hello world"). Every function delegates to the method of the same behaviour, so the two routes cannot diverge. 4D 20 LTS has no singletons, hencenew().Fnd_Text_MD5Encoding— reads the default MD5 charset, or sets it for every later call.- New trailing optional parameters, all backwards compatible:
Fnd_Data_FormatWebURLandFnd_Data_FormatTexttake an options object;Fnd_Data_EmailAddressErrortakes a pointer for the description;Fnd_Text_TextToMD5takes a charset. Fnd_Data_FormatPhoneandFnd_Data_FormatPostalCodewidened their style parameter fromIntegertoVariantand dispatch onValue type, so an options object is accepted where the legacy style constant still works.Fnd_Data_ParseNamenow also returns an object, in addition to the output pointers it has always filled.