Skills
TC AWC Stylesheets
Skill
tc-awc-stylesheets. Author, register and verify Active Workspace style sheets (XRT / XML Rendering Templates) on Teamcenter - putting custom content on an Overview or Details tab, working out which style sheet a surface actually resolves, carrying real markup via an SWF module htmlPanel, and the traps where a write returns 200 and changes nothing. Use for any AWC style sheet, XRT, summary panel, custom dashboard, declarative view, or "my panel does not appear" investigation.
"Style sheet" in Active Workspace means XRT, an XML Rendering Template, not CSS. XRTs live in
XMLRenderingStylesheet datasets, are read from the database on every render, and control the
secondary work area (the Overview / summary panel) plus the Create / Save As / Revise panels. A
change lands with no WAR rebuild, no kit build and no BMIDE. CSS theming is a different animal and
lives in the SWF dev environment.
Verified against TC 2606 on the TC2606 VM. See tc-vm-operations for reaching the tier and
tc-soa-session for connecting.
Start here: the resolution chain is the whole problem
The single most expensive mistake is registering a correct style sheet against a type the surface never consults. It presents as "my panel does not appear", and every subsequent theory (the markup, the anchor, the module, the code page) is wasted effort. Establish which preference the surface reads before touching content.
Measured on 2606, the surfaces differ:
| What is selected on screen | Preference AWC resolves | OOTB value |
|---|---|---|
| BOM occurrence in the Content tab | AWC_Awb0DesignElement.SUMMARYRENDERING |
Pmg1CPGAwb0DesignElementSummary |
| Generic occurrence | AWC_Awb0Element.SUMMARYRENDERING |
Awb0ElementSummary |
| Item revision opened directly (lands on Details) | AWC_ItemRevision.showObjectLocation.SUMMARYRENDERING |
Awb0ItemRevSummaryForShowObjectLocation |
| Item revision, generic fallback | AWC_ItemRevision.SUMMARYRENDERING |
Awp0ItemRevSummary |
Two consequences that cost a full session each:
- The Content tab selects an OCCURRENCE, not an ItemRevision. Registering on
ItemRevisionand testing in a structure means the client never reads your registration. - Specificity wins. The bare
AWC_ItemRevision.SUMMARYRENDERINGloses to any location-qualified name that already exists (AWC_<Type>.<location>.<sublocation>.SUMMARYRENDERING). Registering the least specific name and seeing no change is not evidence your XRT is broken.
Registration is the hierarchical preference AWC_<Type>.<SUMMARY|CREATE|SAVEAS|REVISE>RENDERING,
with POM inheritance up the type tree.
The full precedence chain
Documented in the Customization book under "Assign style sheets to a workspace". Most specific wins, and a workspace-qualified name outranks every non-workspace name:
AWC_<workspaceId>.<type>.<location>.<sublocation>.<stylesheetType>
AWC_<workspaceId>.<type>.<location>.<stylesheetType>
AWC_<workspaceId>.<type>.<stylesheetType>
AWC_<type>.<location>.<sublocation>.<stylesheetType>
AWC_<type>.<location>.<stylesheetType>
AWC_<type>.<stylesheetType> <- the one everyone reaches for first
A vanilla 2606 tier already ships workspace-qualified entries (AWC_TcCAEWorkspace.ItemRevision.*,
AWC_TcPhysicalTestEngineerWorkspace.ItemRevision.*, AWC_TcXSimplifiedWorkspace.*), so check
whether one covers the workspace your test user resolves to. A user whose group has no workspace
mapping falls through to the AWC_Default_Workspace preference.
Enumerate what already exists before you register anything
This is the step that ends the guessing. Export the site preferences and grep:
preferences_manager -u=infodba -p=infodba -g=dba -mode=export -scope=SITE -out_file=C:\kits\prefs.xml
★ The flag is -out_file, not -file; -file errors with
The output file (parameter '-out_file') is not specified. Then grep the export for
name="(AWC_[^"]*RENDERING)" and read the <value> out of each block. On this tier that surfaced
AWC_ItemRevision.showObjectLocation.SUMMARYRENDERING and
AWC_ItemRevision.showObjectLocation.OccurrenceManagementSubLocation.SUMMARYRENDERING already
present and already outranking the name being set. Also grep AWC_Awb0 for the occurrence types.
How to find which one a surface actually uses
Do not reason about it. Two grounded methods, both cheap:
- Match the tab list. Open the candidate OOTB XRT and compare its tab titles against the tabs
visible in a screenshot of the surface.
Pmg1CPGAwb0DesignElementSummarywas identified this way: its Overview / Markup / Weight and Balance / Cost / Parameters list matched the screenshots exactly, andAwp0ItemRevSummarydid not. - Capture the client's own call. The internal op is
Internal-AWS2-2016-12-DataManagement/getDeclarativeStyleSheets(NOTgetStyleSheet, which exists at that version but faults 214086 on every guessed struct). Seetc-capture-awc-calls. It returns only the PAGE LIST even withprocessEntireXRT:true; section content comes from a separate sublocation load.
Where the OOTB sources live on the tier
Verified present on the 2606 VM, 2026-08-07:
C:\apps\PLM\tc_root\install\aws2\data\ 171 .xml style sheets
C:\apps\PLM\tc_root\install\aws2\data\Awp0ItemRevSummary.xml
C:\apps\PLM\tc_root\install\aws2\data\import_aws2_stylesheets.txt OOTB manifest
C:\apps\PLM\tc_root\install\pmg1cpmgmtaw\data\Pmg1CPGAwb0DesignElementSummary.xml
Per-module style sheets live under their own module's data directory, not in aws2\data. Search
tc_root\install recursively rather than assuming.
Awp0ItemRevSummary.xml is 462 lines, UTF-8 with BOM, and is itself a composition of roughly 30
<inject type="dataset"> blocks plus one <inject type="preference">. Patch it textually, not by
an XML round trip, so the admin's diff is one line.
Getting real markup onto the panel
<inject type="dataset"> of an HTML dataset is not a path on 2606. Every OOTB "HTML dataset" is
a one-line pointer to a declarative view from a built SWF module: Rb0DashboardReportViewer.html is
51 bytes and contains only <aw-include name="Rb0DashboardReport"></aw-include>. There is no
raw-HTML route into an Overview tab.
The supported route is htmlPanel declarativeKey=, backed by a real SWF module build. Proven
working on 2606 and re-verified in pixels on 2026-08-07 with the tier healthy: a custom coverage
dashboard rendered on the Overview tab of a selected occurrence, styled by AWC's own theme, with its
bars, burn-down and table intact. Render time tracks tier health directly: 13 s starved, 7 s
part-fixed, 2 s healthy.
★★★ SETTLED by build + render: THREE selection accessors all work. .properties. vs .props. was a false dichotomy.
Measured 2026-08-10 by shipping a five-row probe in the module view and rendering it on
000241/A;1-FuelControlUnit:
1 subPanelContext.selected.properties.object_string.uiValue = [000241/A;1-FuelControlUnit] RESOLVES
2 subPanelContext.selected.properties.awb0ArchetypeId.dbValue = [] (not a property of this type)
3 subPanelContext.selected.props.object_string.uiValue = [000241/A;1-FuelControlUnit] RESOLVES
4 ctx.selected.properties.object_string.uiValue = [000241/A;1-FuelControlUnit] RESOLVES
5 subPanelContext.declarativeKeyContext = [] (no context= on this htmlPanel)
★★ .props., .properties. and ctx.selected.properties. all resolve. A long-running
hypothesis held that the Customization book's .properties. was the correct accessor and .props.
was why binding "did not work". Both work, so the accessor was never the problem and switching to
.properties. fixes nothing.
★ Rows 2 and 5 are empty correctly, and they are the controls that make the positives readable:
awb0ArchetypeId is an occurrence property that this revision type does not carry, and this
particular htmlPanel declares no context= attribute. Without those two, five resolving rows would
have told you nothing about whether the probe could produce an empty at all.
⚠ What the earlier all-empty readings actually were: AWC's "Restore from where you left off?" banner leaving nothing selected, which empties every selection accessor on the page including OOTB ones. A wrong-accessor theory and a no-selection artifact look identical, and the theory survived two sessions because nobody produced a positive control.
⚠ Binding the IDENTITY is solved. Binding the DATA is not.
The view is selection-bound; the view-model DATA is not. data.* comes from
<Key>ViewModel.json, baked at module build time, so the same numbers render for every object of
that type: the coverage dashboard showed the InvertedFlight ledger while the selected occurrence
was an unrelated additive_target_component_metric_2606, and showed it again on that item opened
standalone.
So a panel can correctly name the object it is on while reporting another object's numbers, which
is worse than an obviously generic panel. Per-object data needs the view model to fetch using the
bound id - a dataProviders / SOA action keyed off
subPanelContext.selected.props.item_id.dbValue - not a different accessor. State the snapshot's
scope and date on the panel itself until that exists.
An injected XRT MUST be rooted in <subRendering>, never <rendering>:
<subRendering>
<content visibleWhen="item_id == NNN">
<section title="...">
<property name="object_string"/>
<htmlPanel declarativeKey="..."/>
</section>
</content>
</subRendering>
★ Put a <property> beside the htmlPanel while testing. It splits one ambiguous failure into
two distinguishable ones in a single run:
- section absent entirely -> the registration is still pointed at the wrong type
- section shows the property but not the panel -> XRT is live,
declarativeKeyis not resolving - section shows both -> done
Remove the diagnostic property before any demo.
★ visibleWhen on the injected content is what keeps a site-scoped registration from changing every
other object's Overview tab.
★★ Not every relation reaches the XRT layer at all, by property OR by objectSet
⚠ A type-specific GRM relation does NOT reliably compile into a same-named reference property, and
where it does not, an <objectSet> over it does not work either. Measured end to end on
Seg0Exhibits (Mission Engineering session, cloud2506 then TC2606):
| Route | Result |
|---|---|
<property name="Seg0Exhibits"/> |
renders nothing |
getProperties on the revision, under Seg0Exhibits, seg0Exhibits, Seg0FtCapability, IMAN_Seg0Exhibits |
200 with empty props |
getProperties on the relation uid itself |
InternalServerException |
<objectSet source="Seg0Exhibits.Seg0FtCapability"> in a rendered tab |
section renders, zero rows |
expandGRMRelationsForPrimary / ForSecondary |
works |
The objectSet was verified on 000774, an object with three capabilities confirmed by
expandGRMRelationsForPrimary, on a pool 23 minutes warm. So this is a real negative, not an empty
data set and not a caching artifact.
★★ So Seg0Exhibits does not expose to the XRT layer at all. The only route is
expandGRMRelations, which means an htmlPanel backed by a module that calls it itself - not a
fallback, the only option. Selection binding makes that viable: the panel can read
subPanelContext.selected.props.item_id.dbValue and traverse from there.
★ Do not read an empty section as a style-sheet fault before checking the data exists. On the
same tab two other sections rendered empty purely because no Att0HasParamValue, Seg0Refine or
Seg0Satisfy had been authored on those objects. Three of four sections were correct; only the
relation-backed one was defective. Confirm the relation is populated with expandGRMRelations before
blaming the sheet.
Building the SWF module that backs the htmlPanel
The dev environment ships complete on the tier at C:\apps\PLM\tc_root\aws2\stage (initenv.cmd,
awbuild.cmd, node_modules already installed, 133 OOTB kits under src/repo). Nothing to set up.
awbuild.cmd is the whole pipeline: createNodeModules -> audit -> build -> publish, and it
publishes to the gatewayURL in stage/tem.properties. About 45 minutes on this VM.
Minimal module an XRT htmlPanel can call, copied from OOTB AddProjectCommandXrt in projmgmt:
src/<kit>/module.json {name, description, pathOffset:".", version, author}
src/<kit>/kit.json {name, description, modules:[<name>], version}
src/<kit>/src/assets/html/<Key>View.html
src/<kit>/src/assets/viewmodel/<Key>ViewModel.json can be just {"schemaVersion":"1.0.0"}
Then <htmlPanel declarativeKey="<Key>"/> resolves <Key>View.html.
★ The doc says src/html and src/viewmodel. The real repo uses src/assets/html and
src/assets/viewmodel. Copy the repo, not the book.
★ Plain HTML with inline styles works inside the view; OOTB views wrap content in
<div class="sw-row"><div class="sw-column w-12">. Avoid {{ }} in generated markup, it is template
binding syntax.
★★ Run the build as a scheduled task. Start-Process launched from a WinRM session dies with the
session and leaves no log at all, which looks exactly like a build that never started.
★★★ The AUDIT runs before build and publish, and it is the cheapest check you have
awbuild.cmd runs audit first. A bad kit fails in 90 seconds to 2.5 minutes and nothing is
built and nothing is published, so the previously published site stays live and untouched. Run it
deliberately as a syntax-and-dependency check before you consider a module finished, rather than
discovering problems after a 25-minute build.
Three audit rules that reject perfectly good code, all hit on 2026-08-10:
★★ Non-ASCII characters in src/assets/js fail no-irregular-whitespace. The star and warning
glyphs used liberally in comments in this workspace will fail the build. Plain ASCII in JS.
★★ no-undef fires even on an identifier guarded by typeof x !== 'undefined'. An AMD module
calling a sibling it did not declare is an error, not a warning. Declare it:
define(['js/Xc0Other'], function (Xc0Other) {.
★★★ js-dep-check cannot see exports through an AMD define() wrapper, and reports
"The method X is not exported" about a file where X IS exported. Verified by reading the staged
file - one define, function classifyThread present, classifyThread: classifyThread in the
returned object - and the check still failed.
The OOTB modules that the check resolves use ES module exports, confirmed by following a shipped
view model's deps to its file:
"deps": "js/utils2dViewer" -> 2d-viewer\src\assets\js\utils2dViewer.js
export let setUsePictureSlinging = function( value ) { ...
So a view-model JSFunction action whose deps names an AMD module will fail the audit no matter
how correct the module is. ⚠ One OOTB sample is strong evidence that ES exports work and only
suggestive that AMD never does. The error text offers jsDepCheckMethodNotExistWhitelist in
staticCodeAnalysis_dev.json, which exists precisely because this check false-negatives.
⚠ Retracted before it spread: "no-undef cascades into a false not-exported report." I asserted
that from the two errors appearing together, and relayed it to another session as a finding. Fixing
no-undef left the export error standing alone. They were independent, and the export error was
the real one. Two errors arriving together is not evidence that one causes the other.
★ The build source is on the TIER, not in your repo. stage\src\<kit>\src\assets\... is what
awbuild reads. A local out/awc/module/ copy can be committed, correct and completely irrelevant
to what ships. Stage first, then build.
Verify the module actually shipped, rather than trusting the build log alone:
| Check | Where |
|---|---|
| module was seen | + <kit> line in the build log |
| view was bundled | out/site/static/js/dynamic-<kit>.*.chunk.js exists and contains your markup |
| key is resolvable | out/pathMap.json maps <Key> to src\<kit>\src\assets |
★★★ RETRACTED: "The requested page does not exist" was a WRONG URL, not a workspace grant
⚠ This section previously asserted "Workspace contributions are CONFIG BUILDER data, not build input" and told you to reach Config Builder to grant pages. That conclusion is withdrawn: it was never tested, and the symptom it was built to explain had a different cause.
What actually happened on TC2606, 2026-08-08. The four AWC Reports pages 404'd with
The requested page does not exist. I diagnosed a missing workspace grant, edited
workspace_contribution_reportsjs_TcAuthorWorkspace.json, ran a full build + publish (RC=0,
33 minutes, tier starved to 111 MB free), restarted the pool, and the pages still 404'd. I concluded
the JSON was inert.
Then I opened the UI and clicked the Go to Reports link. It worked immediately, and the address
bar read #/showMyDashboard. Every 404 had come from requesting the module-qualified form
#/com.siemens.splm.reports:showMyReports. The route is the bare page id. The grant was never
missing and the rebuild was never needed.
Two separate lessons, and the second is the one that cost the outage:
- ★★★ A 404 on an AWC location route is a wrong-URL hypothesis first, a configuration hypothesis last. Click through the UI once and read the URL off the address bar. That is one click against a 33-minute disruptive rebuild on a shared tier.
- ★★★ I never tested the correct URL either before or after the rebuild, so I cannot say whether the contribution edit had any effect. The edit is therefore unproven in both directions - neither "inert" nor "works". Do not cite this section as evidence about how workspace contributions behave, because it contains no such evidence. If you need to know, test it deliberately with a route you have already confirmed by hand.
One further measurement taken while cleaning up, which is grounded and worth having. On TC2606 today,
src/solution/workspace_contribution_reportsjs_TcAuthorWorkspace.json is unmodified OOTB (mtime
May 2026) and grants exactly one page:
{ "schemaVersion": "1.0.0", "workspaceId": "TcAuthorWorkspace",
"availablePages": [ "createReportTemplate" ] }
...and infodba reaches #/showMyDashboard, #/showReportBuilderReports and #/showReport anyway.
So either infodba does not resolve to TcAuthorWorkspace, or that per-module file is not what gates
these routes. Both remain open. What is settled is only that the 404 was the URL form.
The one thing in the original section that IS grounded and still holds is the availablePages file
shape and the warning below about switching workspaces wholesale.
The in-product Config Builder route (Customization book, Modify an existing workspace: switch
to Active Architect, Workspace tab, edit availablePages, Save) is a real documented mechanism.
It was simply not needed here, and was never exercised.
★ Related trap when picking a workspace to switch to: compare what it grants against what you
have. On this tier TcQualityManagementWorkspace grants the full reports page set, but switching
the site default to it would have lost 85 pages to gain 5 (easyplan, assemblyPlanning,
compareRevisions, dispatcherRequests among them). Count both directions before switching.
⚠ A workspace contribution is NOT required for a view-only module, and adding one is cargo cult.
src/solution/workspace_contribution_<module>_<Workspace>.json has the shape
{schemaVersion, workspaceId, availablePages:[...]}: it grants a workspace access to pages
(locations / sublocations). A module that only supplies a view invoked by declarativeKey declares
no pages, so there is nothing legitimate to list. Verified by reading the OOTB files, after this was
wrongly proposed as the fix for a panel that was actually a registration problem.
⚠ Chunk filenames on this tier contain efbfbd (the hex of U+FFFD) for ~6000 of 6252 chunks. That
is deterministic, not corruption. Two builds under different accounts (SYSTEM, then Administrator
with chcp 65001) produced byte-identical names. Do not chase it and do not blame a missing panel on
it.
Creating the datasets
Use the shipped utility, not createDatasets over SOA. Documented in the Utilities Reference and
batchable via utility_execution_set:
install_xml_stylesheet_datasets -u=infodba -p=infodba -g=dba -input=<manifest.txt> -filepath=<dir> [-replace]
Manifest lines are datasetName, file.xml. It sets tool_output_formats "ASCII" and file type
SS_TEXT, provenance the SOA create path never applies. Run it inside the TC environment shell
(tc_root\tc_menu\tc_Vanilla_Env.bat); TC utilities exit 0xC0000135 having done nothing if the
profile vars are not sourced first. It does not install HTML datasets; those ship with the module.
Verified named references, read off OOTB datasets rather than documented next to the type:
| Dataset type | Named reference | File |
|---|---|---|
XMLRenderingStylesheet |
XMLRendering |
<name>.xml |
HTML |
HTML |
<name>.html |
Two saved-query matches per dataset name is NORMAL (the dataset plus its version object); OOTB
Awp0ItemRevSummary returns 2 as well. Do not read it as a duplicate.
See tc-datasets-files for the SOA dataset route when you genuinely need it.
Registering: two silent failures
★★ setPreferencesAtLocations' argument is named setPreferenceIn, not preferenceInputs. A
wrong TOP-LEVEL argument name returns HTTP 200, empty ServiceData, no partialErrors, and no
effect. The struct is:
[{ location: {object: NULLTAG, location: "Site"},
preferenceInputs: {preferenceName, values: []} }]
where the inner preferenceInputs is a single struct, not an array. Read the argument name off
PreferencemanagementService.hxx in the SOA client kit rather than guessing. See
tc-soa-payload-shapes for the general class of this failure.
★★ Preference reads are cached per SOA session. After a successful write, getPreferences on the
SAME session still returns the OLD value. Disconnect and reconnect before reading back. This has
twice produced a false "the write failed" conclusion.
⚠ Reconnecting is necessary but NOT always sufficient - see the pooled-tcserver finding below. There are two distinct caches (session and pool) and they are not the same problem.
★★★ A preference written with preferences_manager does not reach POOLED tcservers, and a new
session is not enough. Measured on TC2606, 2026-08-07, registering a summary style sheet:
preferences_manager -mode=import -scope=SITE -action=OVERRIDEreported success.preferences_manager -mode=export -scope=SITEimmediately showed the NEW value, with zero occurrences of the old one anywhere in the file. The database was correct.- Every SOA session, including brand new logins, kept returning the OLD value. So did Active
Workspace. Reconnecting did not help, because the stale copy lives in the pooled
tcserverprocesses, not in the session. Administration-2012-09-PreferenceManagement/refreshPreferences2faulted.- Only
Restart-Service "Teamcenter Server Manager TCDB_PoolA" -Forcein the guest picked it up. Immediately after, a fresh session read the new value and the tab rendered.
So exporting a preference to verify it is necessary and NOT sufficient: the export reads the database, the client reads the pool. Budget a pool restart into any style-sheet registration, and warn other sessions on a shared tier first, since it drops every TC session and returns Active Workspace to the login page.
⚠ OPEN QUESTION: this does not reproduce for every preference, and the difference matters
The finding above was measured on a style-sheet registration (AWC_<Type>.SUMMARYRENDERING).
A different session, same tier, same day, measured the opposite for two other writes, and both
observations are real:
| Write | Preference | Took effect without a pool restart? |
|---|---|---|
preferences_manager -mode=import -scope=SITE -action=OVERRIDE |
Xc0PortAuditEnabled (read by a visibleWhen="{pref:...}" on an XRT <page>) |
YES - flipped to false, the tab vanished from the render; flipped back to true, it returned. No restart either time. |
setPreferencesAtLocations (SOA) |
AWC_Fnd0LogicalBlockRevision.SUMMARYRENDERING |
YES - a fresh session rendered the new host immediately. |
preferences_manager -mode=import |
AWC_<Type>.SUMMARYRENDERING |
NO - stale in every session until the pool restarted. |
So "a pool restart is the only thing that picks it up" is too broad as written. What is not yet separated:
- whether the write path matters (SOA
setPreferencesAtLocationsvspreferences_manager), or - whether the preference's role matters - a style-sheet REGISTRATION looked up once per render
context and cached in the pool, versus an ordinary value read fresh when
visibleWhenevaluates.
The second is the better hypothesis on the evidence, because a preferences_manager write to
Xc0PortAuditEnabled propagated fine while a preferences_manager write to a SUMMARYRENDERING
did not. Do not generalise either way from one preference. Verify the specific preference you
changed actually reached the client, and keep a pool restart in reserve for registrations.
⚠ A "protectionScope resolves this" answer was published on 2026-08-08 and is RETRACTED
For about an hour this file claimed the open question was answered: that protectionScope="User" was
the whole cause, that a Site-scoped registration needs no restart, and that "budget a pool restart"
was too broad. That was wrong. The caching finding above stands. The retraction is kept because
the way it went wrong is more useful than the claim was.
- A
SUMMARYRENDERINGregistration verified correct by export would not render. A marker test - renaming a tab toOverview-XC0MARKin the host - proved the host was not being served at all, ruling out markup,visibleWhen, tab order and the overflow chevron separately. That part is solid and is the useful technique here. - Re-registering at Site scope made the tab appear immediately, with no restart. Conclusion drawn: scope was the cause.
- Confounded. The VM had been restored from a checkpoint ~50 minutes earlier and
Teamcenter Process Managerrestarted ~20 minutes earlier, so the pooled tcservers held a cold preference cache. The write landed before anything had cached the old value. Two variables moved and I credited the wrong one. - Falsified by a controlled test. Both registrations were pointed at a different host
(
Xc0ScftaOnly, which deliberately omits the Port Audit inject) and verified by export asprotectionScope=Site, valueXc0ScftaOnly. The client went on rendering the Port Audit tab, which exists only in the other host. A correct, Site-scoped, verified write was invisible.
★★★ So: exporting a preference proves the DATABASE, never the client. Budget a pool restart into any style-sheet registration, or accept the change is invisible until something recycles the pool.
★★★ SETTLED: the tcserver pool caches PARSED style-sheet content. Every change needs a recycle.
Answer five, and the only one from a pre-registered controlled test. Read this one and skip the archaeology below it.
Method, because the method is why this one can be trusted:
- Renamed one tab in the host dataset to
Trade V2CHECK. Installed with-replaceon a 26-minute warm pool. - Read the dataset back out of Teamcenter through FMS and confirmed the newest version
(
Xc0LBRevSummaryPortAudit;7) held the marker, identical after newline normalisation. So the database provably had it. - Rendered on a 29-minute warm pool: marker absent.
- Wrote the interpretation down before the restart, and another session recycled the pool with zero pid overlap and deliberately issued no HTTP to AWC, so my render was the first request the new pool saw.
- Rendered again: marker present.
| Outcome | Meaning | Result |
|---|---|---|
| marker appears after recycle | pool cached parsed content | THIS |
| marker still absent after recycle | client resolves a non-newest dataset version | ruled out |
Conclusions, both directions:
- ★★★ The pool caches parsed style-sheet content. A dataset content change is invisible to every
client until
Teamcenter Server Manager TCDB_PoolArecycles - just as a preference value change is. Budget a pool recycle into any style-sheet change, content or registration. - ★★
install_xml_stylesheet_datasets -replaceIS a safe deploy verb. It adds a version, and the newest version is what gets served after a recycle. The "-replace silently did nothing" reading that recurs in this file was always the cache, never the utility.
★ Verify a deploy in two independent steps, because they fail differently. First assert the DATABASE holds your bytes: fetch the stored file through FMS and diff. Then assert the CLIENT renders it, after a recycle. The utility's own output tells you neither - a replace logs no per-dataset line at all and exits 0 either way.
★ Normalise newlines before diffing. install_xml_stylesheet_datasets rewrites LF to CRLF, so a
byte comparison of a stored dataset against your generated file reports a false mismatch on a clean
deploy - which reads exactly like the silent-no-op above.
★ Read the HIGHEST dataset version, never the un-suffixed row. The row whose object_string
carries no ; is not the newest; measured elsewhere it shared its ImanFile with ;3 while ;5
held the fresh content. And getFileReadTickets needs files with the ImanFile uid from
ref_list; passing datasetFiles returns tickets:[].
⚠ Superseded: ASSUME EVERYTHING IS CACHED UNTIL THE POOL RECYCLES
⚠ A "register once, then only change CONTENT" rule was published here on 2026-08-08 and is RETRACTED the same day. It claimed dataset content changes are not cached and so need no restart. The one clean test contradicts it.
| Change | Cached? | Evidence |
|---|---|---|
| Preference value (host A -> host B) | YES | Registrations repointed to Xc0ScftaOnly, Site scope + new value verified by export. Client kept serving the old host's tab. Cache temperature irrelevant - this test does not depend on it. |
| Dataset content of the registered host | UNPROVEN, evidence says cached | Content-only change (renaming a tab to Trade WARMTEST) installed on a warm pool: did not appear. The earlier "it appeared instantly" observation was taken 4 minutes after a pool restart. |
The timestamps, because the whole lesson is in them:
pool restarted 13:26:44 (by another session, mid-experiment)
tcservers started 13:27:09
"three tabs appeared!" 13:30:53 <- 4 min into a COLD pool. Confounded.
content-only change 13:36:31 <- warm pool
render 13:39:42 <- marker did NOT appear
★★ Two candidate mechanisms remain and I did not separate them: the pool caches parsed stylesheet
content, or install_xml_stylesheet_datasets -replace adds a dataset VERSION and the client keeps
serving an older one. Both predict what was seen. Do not assert either.
Operationally, until someone separates them: treat every style-sheet change - preference OR dataset content - as invisible until the tcserver pool recycles. That is the conservative reading and it costs nothing but a restart you were probably budgeting anyway.
★★ This also dissolves the multi-session collision. One type has one summary style sheet and a
second registration REPLACES rather than merges, so two sessions racing the same preference means the
loser's tab vanishes with no error. Composing into one host fixes it: three sessions' tabs now ship
from a single registered host on this tier, each as its own <inject>:
<page title="Port Audit">...</page> <!-- injected file is rooted <subRendering><page> -->
<inject type="dataset" src="Xc0ScftaTab"/>
<page title="Trade"> <!-- injected file is rooted <subRendering><content> -->
<inject type="dataset" src="Msn0SolutionClassTradeTab"/>
</page>
★ Two injected-file shapes exist and they nest differently. A file rooted in
<subRendering><page> contributes its own tab: inject it at top level. A file rooted in
<subRendering><content> is a fragment: the HOST must supply the <page> wrapper around the inject.
Getting this backwards double-nests or contributes nothing, silently.
★★ The inject attribute is src=, not name=. OOTB uses src throughout. A handover package
here specified name=, which does not resolve and produces exactly the silent no-tab outcome this
file is full of.
⚠ Tab order is document order, and AWC pushes surplus tabs behind an overflow chevron most users
never open. With 11 tabs on this host the chevron is live, so placement is a real decision, not
cosmetic. Probe the rendered tab list rather than assuming - [role="tab"] labels off the live page.
★★★ protectionScope is DISPROVEN as a factor, not merely untested
Upgraded from "untested" to disproven later the same day, on a counting argument that needs no recycle. SITE export of TC2606:
*.SUMMARYRENDERING preferences 1,803 total
protectionScope="User" 959
protectionScope="Site" 844
AWC_ItemRevision.SUMMARYRENDERING protectionScope="User" <- and every ItemRevision renders fine
959 of 1,803 are User-scoped, including the OOTB registration every ItemRevision on the tier renders
through. If a User-scoped SUMMARYRENDERING were silently unresolvable, most summary panels on the
tier would be blank. They are not.
★★ What protectionScope actually means: the LOWEST scope at which a preference may be
overridden - not the scope the effective value lives at. A User-scoped preference is one a user is
permitted to override; the site value still resolves underneath. So it was never a gate on
resolution at all.
⚠ Do not Site-scope a registration and recycle in the same pass, or you will credit the wrong half again. That is precisely how this wrong mechanism was born: a Site-scoped rewrite coincided with a pool recycle, the tab appeared, and scope took credit that belonged to the cache.
Everything on this surface reduces to the one cache: registration changes and dataset content
changes are both invisible until TCDB_PoolA recycles. One mechanism explains all of the evidence,
which is a cleaner story than the two-gate model that briefly replaced it.
Historical note, kept because the shape recurs: this section previously read "whether
protectionScope matters as well is now untested - it may be a real second factor". That hedge was
too generous. The scope observation was a true reading of the tier offered by another session,
but the mechanism implied by it was never verified, and it displaced a correct hypothesis for two
hours. Same failure as everywhere else in this file: the observation was clean, the cause bolted
onto it was a guess.
★★★ The generalisable trap, and the reason this is worth reading: a cold cache after a restart makes a broken write look fixed. If a change starts working right after any restart, revert it and re-apply it on a warm system before believing the fix. This produced a confidently-wrong conclusion that was committed, deployed to a live site, and relayed to another session, all within the hour.
★★★ It then happened AGAIN, the same afternoon, to the replacement conclusion. A second session restarted the pool at 13:26:44 while this experiment was open and told me by a message that queued behind an in-flight turn. I installed and rendered at 13:30, saw new tabs appear, and published "dataset content is not cached". Same confound, same cause, four minutes apart. Three published conclusions in one afternoon were all confounded by restarts I did not know about.
Two rules came out of it, and they are worth more than any of the retracted findings:
- For the operator: do not perform an infrastructure operation on a shared tier while another session has an experiment open, until that session has acknowledged. A queued message is not an acknowledgement. If it is urgent, do it and say so loudly, so the other session can discard measurements from that window rather than publish them.
- For the experimenter: timestamp your own measurements and ask what restarted recently before
publishing a mechanism.
(Get-Process tcserver | Sort-Object StartTime | Select -First 1).StartTimegives the pool's age in one call. If your measurement is younger than the pool, it proves nothing about caching.
★★ Stop asserting mechanism from a single observation on shared infrastructure. Publish what was measured, with the pool age beside it, and label the mechanism unproven. Every wrong call here came from naming a cause after one uncontrolled run.
★★★ Pre-registering the OBSERVATION is not the same as pre-registering the CAUSE
The subtlest trap of the lot, and the one that survives all the discipline above. Pre-registration fixes what you will see; it does not validate the explanation you attach to it. A cause smuggled into a branch label rides along unverified and then looks earned, because the surrounding rigour was real.
Worked example, same tier, same day as everything above. The branch was written in advance as:
error persists on a cold pool -> a summary report requires all three
ReportChartNslots
The error did persist on a cold pool. The condition was clean, answerable, and correctly predicted. The explanation was wrong: three slots were already declared, and the real fault lay in the table-column parameters. A looser process would have published "summary reports require three chart slots" on the strength of the condition being met, and it would have read as a properly controlled finding.
★★ So split them. Pre-register only what you can observe:
GOOD branch: "does the marker render after a recycle, yes or no" <- observable
BAD branch: "marker absent -> the client resolves a stale VERSION" <- cause bolted on
Derive the mechanism after the observation lands, and treat it as a fresh unproven claim needing its own test. This is nastier than an ordinary wrong guess precisely because the rigour around it is genuine, so the usual smell test ("was this controlled?") returns yes.
★ Distinction named by the TC VM session after it caught this happening.
★★ Get-Service misreports Teamcenter service state over WinRM. On that same healthy tier it
listed Teamcenter Server Manager TCDB_PoolA, Teamcenter WebTier and every other TC service as
Stopped while they were serving requests. Get-CimInstance Win32_Service and the process table
both said Running (pool pid 14196, web tier 16252). Taken literally, the first reading says "the tier
is down" and sends you to start services on a working system. Use Win32_Service and the process
list. Related: tcserver processes are spawned on demand and reaped, so a count of 0 means an idle
pool, not a stopped one: it read 0, then 8, then 0 within minutes.
★★★ A failed write and a failed SESSION look identical at the call site. Two preference writes
returned FAIL that had not been rejected at all: the login underneath them had failed with
No business server instances are available. The identical calls succeeded once the tier had memory.
Check the session before recording a constraint about preferences. See the next section.
The tier must be healthy before you diagnose anything
Style-sheet symptoms and tier-starvation symptoms are indistinguishable from the browser: skeleton
placeholders, an empty structure tree, a red "The server encountered an error" toast, and
No business server instances are available on login.
Measured on this VM, 2026-08-07: guest memory demand 15.4 GB against an 11.77 GB cap, guest available memory down to 182 MB, and AWC login failing outright. None of it was an XRT problem.
Check before diagnosing:
Get-VM -Name TC2606 | Select-Object @{n='AssignedMB';e={[math]::Round($_.MemoryAssigned/1MB)}},
@{n='DemandMB';e={[math]::Round($_.MemoryDemand/1MB)}}
If Demand exceeds Assigned the tier is paging and nothing you observe in the browser is evidence about your style sheet.
Measured either side of the fix on this VM, 2026-08-07:
| starved | fixed | |
|---|---|---|
| Assigned / Demand | 11.77 GB / 15.4 GB | 13.9 GB / 11.8 GB |
| Guest available | 182 MB | 6069 MB |
| tcserver processes | 24 | 8 |
| AWC login | No business server instances are available |
ok |
| Panel render | 13 s, or skeletons | 2 s |
Demand below Assigned is the check that matters, not free memory in the guest.
★ The tcserver pool is the dominant consumer and is configurable.
C:\apps\PLM\tc_root\pool_manager\confs\TCDB\serverPool.properties:
PROCESS_MAX=30 ceiling, only matters under load
PROCESS_WARM=3 warm spares
PROCESS_TARGET=0000 4 time-of-day schedule: "HHMM count"
Shipped as PROCESS_TARGET=0000 24, which held 24 warm tcservers (5.78 GB measured) on a
single-user demo VM. Back the file up, edit, then restart Teamcenter Server Manager TCDB_PoolA.
⚠ Do not cut it too far. HARD_TIMEOUT_EDIT / HARD_TIMEOUT_READ default to 28800 seconds
(8 hours), so a handful of held sessions pin servers all day. A target of 4 exhausted the pool
immediately and reproduced No business server instances are available. 8 target / 3 warm was stable.
★★★ The trim above has NEVER actually been applied to the TC2606 VM. Do not read this section as
describing the tier's current state. Checked directly 2026-08-08:
serverPool.properties mtime is 07/14/2026, unmodified, carrying the shipped
PROCESS_TARGET=0000 24 and PROCESS_WARM=5, and there is no backup file beside it (which the
recipe above tells you to create first, so its absence is the tell). The "fixed" column in the table
above was reached by some other means and did not survive; the VM has been restarted since.
So expect to find this tier starved, and expect the pool to be the reason. Measured the same day, two hours after a guest restart:
| value | |
|---|---|
| VM Assigned / Demand | 16384 MB / 22773 MB |
| Guest free | 465 MB |
| tcserver processes | 24 |
| CPU | 3% (it is paging, not computing) |
★★ Raising the VM's memory is NOT an available lever here, so do not reach for it first.
Get-VMMemory -VMName TC2606 reports dynamic memory with Maximum = 16384 MB, which is exactly what
is already assigned, and the host has 32678 MB total with only ~2092 MB free. There is no headroom
to give. Reducing demand inside the guest is the only real fix.
⚠ The pool restart is disruptive and this VM is SHARED. Restarting
Teamcenter Server Manager TCDB_PoolA drops every open AWC session and any in-flight utility run,
and several workstreams (SCFTA, integration-readiness, reports) use this tier concurrently. Confirm
before restarting it rather than treating tier health as a free unilateral fix.
★ Symptom to recognise before you blame your own work: WinRM Invoke-Command calls that ran in
seconds start timing out at 2 to 5 minutes, while light commands (reading a file, listing
processes) still return promptly. That split is the signature of a starved pool: anything that needs
a tcserver session queues, anything that does not is fine. An aw_import_export_reports call that
times out in that state has an unknown outcome, not a failed one. Re-read before re-running.
★★★ A SUCCESSFUL SOA READ DOES NOT CLEAR THE TIER. It is not a control.
The most persuasive wrong conclusion this environment produces is "the tier is fine, so the fault must be mine". It is usually reached by exactly one move: running a light SOA call, watching it succeed, and treating that as evidence the tier is healthy.
It is not. A getProperties read and a full Active Workspace render have completely different
tcserver demands, which is the same split described immediately above. On a starved pool the light
call returns promptly while the render 500s. So a passing SOA read cannot discriminate, and using
it as a control means the tier stays "ruled out" while it is in fact the cause.
Twice in two days on TC2606 this produced a fabricated defect that was more persuasive than an ordinary mistake, because it was carefully argued from real measurements:
| symptom | attributed to | actually |
|---|---|---|
| search-backed reports all returning 0 | "the full-text index has stopped serving" | a checkpoint restore, different database |
| every AWC render 500ing with Failure in loading Summary | "the shoot-awc harness builds a session AWC rejects" | tier starvation, demand 20.2 GB against a 16.4 GB cap |
In the second case the author had ruled the tier out because their SOA calls kept succeeding, and had ruled out the objects and the style sheet correctly. Three good eliminations, one control that could not discriminate, and the conclusion landed on the only suspect left.
★★ What actually discriminates, in order of cost:
- Read VM memory first, before forming any hypothesis.
DemandaboveAssignedmeans every observation you are about to make is unreliable. One command, no session needed. - Look for a time correlation. The same operation on the same object type succeeding earlier and failing later is tier state; a structural defect cannot produce opposite outcomes on the same input ninety minutes apart.
- Only then blame the tool, and re-run the failing case once the tier is healthy before publishing.
⚠ And do not bank a failure on a starved tier even when it supports you. A failing render there is uninterpretable in both directions - it cannot confirm a defect and it cannot refute one.
★★ Prefer the test whose outcomes are ASYMMETRICALLY informative, and run it even under bad conditions when the informative direction is the cheap one. Here: a render succeeding on a starved tier is conclusive (a structural defect cannot succeed under worse conditions than the ones it supposedly failed in), while a render failing settles nothing. That asymmetry made one attempt on a 0.55 GB-free tier worth more than waiting a day for a clean one - and it delivered, so waiting would have bought a weaker answer, not a better one.
★★★ The re-read rule fires hardest when you are confident, which is exactly when nobody looks. The ballooning measurement above was not found by insight. A pool recycle was run expecting it to work, and guest free memory was re-read afterwards only because re-reading the artifact is the standing rule - at which point the number had gone the wrong way. Every mechanism-succeeded- effect-absent finding in this file was caught the same way. That is the case for the rule rather than a demonstration of skill: it earns its cost on the runs you were sure about.
Verifying: both kinds of evidence lie, in opposite directions
Capital_TC_Integration/bin/shoot-awc.mjs logs in over JSON REST SOA, transplants the session cookie
into Edge via the DevTools protocol, and dumps a screenshot plus page text plus console errors plus
requests still in flight.
node bin/shoot-awc.mjs --uid <revUid> --wait-for "<a string only your panel renders>" \
--settle 45000 --out shot.png [--click-text "Details"] [--eval-file probe.js] [--headed]
Three failure modes worth knowing, all hit on 2026-08-07:
- ★★ The page TEXT can be healthy while the pixels are skeletons.
document.body.innerTextreturned the full property list and the whole dashboard while the visible layer showed grey bars. Reporting from the text dump produced a false "it renders". - ★★ A DOM assertion with guessed selectors is worse than no assertion. A probe counting
[class*="skeleton"]returned0on a page that was visibly grey, because those elements do not carry that class. Sampledocument.elementFromPoint(x, y)and report the real class chain instead of guessing at class names. - ★★★ Grey blocks in a HEADLESS capture are not proof of a broken render. Sampling the DOM at
those exact coordinates returned
SPAN.sw-property-namecontaining the text "ID", transparent background, no animation. The same page rendered clean in--headed.--headedis the tie-breaker and should decide any dispute between a screenshot and the DOM.
★ --eval-file, not --eval: PowerShell strips inner double quotes when handing a string to a
native exe, which silently turns any selector containing quotes into a JS syntax error.
★ --wait-for polls for a string and reports honestly whether it ever appeared, which beats a fixed
settle. A fixed delay screenshots a cold tier's skeletons and looks exactly like a page that rendered
nothing.
Rollback
One preference write per registration:
AWC_ItemRevision.SUMMARYRENDERING -> Awp0ItemRevSummary
AWC_Awb0DesignElement.SUMMARYRENDERING -> Pmg1CPGAwb0DesignElementSummary
AWC_Awb0Element.SUMMARYRENDERING -> Awb0ElementSummary
Nothing else has to be undone; the datasets can stay. Reconnect before reading back (see the caching trap above).
⚠ Diagnostic registrations are easy to leave behind. One left on this tier was
AWC_ItemRevision.showObjectLocation.OccurrenceManagementSubLocation.SUMMARYRENDERING =
Xc0ItemRevSummaryV3, a patched summary whose injected section contained only the htmlPanel.
Enumerate every *RENDERING preference you set and revert the scaffolding, not just the one that
worked.
Retracted conclusions, kept so nobody re-derives them
⚠ "Any custom injected section that actually RENDERS hangs Active Workspace" is WRONG. So is the
table concluding that <property> renders while <label> and injected HTML hang. Both were measured
against a preference the client was not reading, so every variant "hung" for the same reason: the
surface was never consulting that style sheet at all. Retracted by the session that found the
registration bug, 2026-08-07.
✅ Now positively disproven, 2026-08-07, with a control. A custom injected <page> carrying
<section> + <property> + <htmlPanel> rendered; the identical page differing only by an added
<label text="..."/> and <break/> rendered the same. <label> is fine on 2606. See
tc-awc-custom-tab.
⚠ A grey "skeleton" screenshot is not evidence, and --headed does not settle it. The other half
of the original mistake was the capture itself: Page.captureScreenshot with
captureBeyondViewport: true re-renders into an off-screen surface that Active Workspace's
virtualized panels never paint into, so a fully populated DOM captures as tidy grey placeholder bars
in headless AND headed alike. Capture fromSurface: true with that flag off, and arbitrate any
dispute with document.elementFromPoint rather than with the image or with guessed selectors.
⚠ "Every ItemRevision's full-page Details tab renders skeleton placeholders forever" is not
reproducible once the tier has memory and the render is checked headed. The original observation is
consistent with tier starvation plus the headless capture artifact above. If you see it, check
memory and run --headed before concluding there is a defect in AwDetailsSublocation.
Documentation
Release-matched 2606 set (confirm a release by opening a PDF title page, the collection IDs are opaque):
| Document | Path |
|---|---|
| Active Workspace Customization | D:\Siemens\Help Server\collections\documentation\external\PL20251212545240207\en-US\tc_help\Active Workspace Customization.pdf |
| Active Workspace Fundamentals | same directory, Active Workspace Fundamentals.pdf |
Teamcenter Utilities (for install_xml_stylesheet_datasets) |
same directory, Teamcenter Utilities.pdf |
| Teamcenter Server Customization | same directory |
The 2506 equivalents are under ...\external\PL20241125556497283\... and are a different release.
Use 2606 for this VM.
Local help server: http://localhost:51000. pypdf extracts these PDFs cleanly, which is the way to
check a utility flag claim rather than trusting a write-up.
★ The Siemens Web Framework reference and the Storybook / UI Pattern Library are NOT in the local
D: help set. Configuration links out to /doc/282219420/PL20251212546352891.swf and .storybook;
that collection ships only AdminDataReport / DataModelReport / ITKFunction / TcServerInterfaces. If
you need SWF module or declarative-view detail, it is not on this machine.
Related skills
tc-awc-custom-tab (contributing a whole TAB rather than a section: the top-level inject pattern,
tab order and the overflow chevron, visibleWhen scoping, and the capture traps),
tc-vm-operations, tc-soa-session, tc-soa-payload-shapes, tc-capture-awc-calls,
tc-datasets-files, tc-query-discovery, tc-soa-docs-navigation.
Generated from skills/tc-awc-stylesheets/SKILL.md in the tc-automation-skills library, which is the canonical copy and also serves as the agent skill set for Teamcenter work.